#!/usr/bin/perl -I/usr/local/sauron
#
# sauron - generates BIND/DHCP/printer configuration files from database
#
# Copyright (c) Michal Kostenec <kostenec@civ.zcu.cz> 2013-2014.
# Copyright (c) Timo Kokkonen <tjko@iki.fi>  2000-2005.
# $Id: sauron,v 1.105 2008/02/28 08:42:06 tjko Exp $
#
require 5;
use Net::Netmask;
use Getopt::Long;
use MIME::Base64;
use Crypt::RC5;
use Sauron::Util;
use Sauron::DB;
use Sauron::BackEnd;
use Sauron::Sauron;
use Net::IP qw(:PROC);
load_config();

$bind_conf=0;
$dhcp_conf=0;
$dhcp6_conf=0;
$printer_conf=0;

$dhcp2_mode = ($SAURON_DHCP2_MODE ? 1 : 0);

%yes_no_enum = (D=>'',Y=>'yes',N=>'no');
%check_names_enum = (D=>'',W=>'warn',F=>'fail',I=>'ignore');
%zone_type_enum = (M=>'master',S=>'slave',H=>'hint',F=>'forward');
%forward_enum = (D=>'', O=>'only', F=>'first');
%algorithm_enum = (157=>'hmac-md5');

$ZFORMAT = "%-22s %6s %2s  %-6s %s\n";

@bind_options = ( ['nnotify','notify'],
		  ['recursion','recursion'],
		  ['authnxdomain','auth-nxdomain'],
		  ['dialup','dialup'],
		  ['multiple_cnames','multiple-cnames'],
		  ['rfc2308_type1','rfc2308-type1']
		);

$failover_peer_name = 'foobar';

$tmp_extension = ".tmp.$<.$$"; # generate extension for temp files
%open_tmpfiles = ();

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

sub expand_zonefile_path($$) {
  my($path,$val) = @_;

  my($tag,$tmp);

  if (($tag) = ($path =~ /(\%N+)/)) {
    $tmp = substr($val,0,length($tag)-1);
    $path =~ s/$tag/$tmp/g;
  }
  
  return $path;
}

sub expand_dhcp_macro($$) {
    my($str,$var) = (@_);
    my($k);

    if ($str =~ /\%\{\w+\}/) {
	$str.= "\t# macro eval";
	foreach $k (qw(domain ether fqdn host)) {
	    if ($str =~ /\%\{($k)\}/) {
		$str =~ s/\%\{$k\}/$$var{$k}/ge;
	    }
	}
    }		 

    return $str;
}

sub clean_up() {
  my(@q,$i,$count,$t);

  print "Cleaning up database...\n";
  print "(removing hosts that have been expired more than ",
	"$SAURON_REMOVE_EXPIRED_DELAY days)\n" if ($opt_verbose);

  fatal("SAURON_REMOVE_EXPIRED_DELAY not defined!")
    unless ($SAURON_REMOVE_EXPIRED_DELAY > 0);
  $t=$time_now_ticks - (86400 * $SAURON_REMOVE_EXPIRED_DELAY);

  db_query("SELECT h.id,h.expiration,h.domain,h.type " .
	   "FROM hosts h, zones z " .
	   "WHERE z.server=$serverid AND h.zone=z.id " .
	   " AND h.expiration > 0 AND h.expiration < $t " .
	   "ORDER BY h.expiration",\@q);
  error(db_errormsg()) if (db_errormsg());
  $count=@q;

  # delete expired hosts
  for $i (0..$#q) {
    print "Purging: $q[$i][2] (id=$q[$i][0])   ".localtime($q[$i][1])."\n"
      if ($opt_verbose);
    error("Cannot remove host: $q[$i][2] (id=$q[$i][0])")
      if (delete_host($q[$i][0]) < 0);
  }
  print "$count expired host(s) purged.\n";

  # error("Database vacuum failed!") if (db_vacuum() < 0);
}


sub make_mail_notify_list() {
  my(@q,$i,$user,$date,$mode,$lst,$line);

  db_query("SELECT h.id,h.domain,z.name,h.cdate,h.mdate,h.cuser,h.muser " .
	   "FROM hosts h, zones z " .
	   "WHERE h.zone=z.id AND z.server=$serverid " .
	   " AND (h.cdate > z.serial_date OR h.mdate > z.serial_date) ".
	   "ORDER BY z.name,h.domain",\@q);

  for $i (0..$#q) {
    $mode=($q[$i][3] > $q[$i][4] ? 1 : 0);
    $date=($mode ? $q[$i][3] : $q[$i][4]);
    ($user=($mode ? $q[$i][5] : $q[$i][6])) =~ s/^\s+|\s+$//g;
    $line = sprintf "%-30s %6s  %-20s  %s",
              $q[$i][1],($mode?'Create':'Modify'),"".localtime($date),$user;
    $mailnotify{$user}=[] unless ($mailnotify{$user});
    $lst=$mailnotify{$user};
    push @{$lst}, $line;
  }
}

sub mail_notifications() {
  my ($u,%user,$i,$lst);

  foreach $u (sort keys %mailnotify) {
    if (get_user($u,\%user) < 0) {
      error("cannot get user record for: $u ".db_errormsg());
      next;
    }
    next unless ($user{email_notify} && $user{email});
    $lst=$mailnotify{$u};
    print "Sending email notification to: $u ($user{email})...\n"
	if ($opt_verbose);

    open(PIPE,"| $SAURON_MAILER $SAURON_MAILER_ARGS") || error("pipe failed");
    print PIPE "From: Sauron <$SAURON_MAIL_FROM>\n",
               "Subject: [sauron] update notification ($servername)\n",
               "To: $user{email}\n\n",
	       "Following changes from you are now in effect:\n\n";
    for $i (0..$#{$lst}) {
      print PIPE "$$lst[$i]\n";
    }
    print PIPE "\n\n";
    close(PIPE);
    error("Error sending email notification to $u!") if ($? != 0);
  }

}

sub make_dns() {
  $tiny_file="data";
  $root_file="@";
	
  $zones_only=($server{zones_only} eq 't' ? 1 : 0);
  $bind_filename=($zones_only ? 'named.zones':'named.conf');


  print "BIND configuration\n";
  $time_now = localtime;

  make_mail_notify_list() if ($mailnotify_mode);
  print "Generating $bind_filename...\n" if ($bind_conf);
  print "Generating $tiny_file\n" if ($tiny_conf);
  
  ##################################################
  # named.conf

if ($bind_conf) {
  open(BINDFILE,">$bind_filename$tmp_extension")
    || fatal("Cannot create $bind_filename$tmp_extension!");
  $open_tmpfiles{$bind_filename . $tmp_extension}=$bind_filename;
  print BINDFILE
    "// $bind_filename -- automagically generated by Sauron v$VER\n//\n",
    "// created by $user\@$host at $time_now\n//\n",
    "// server: $server{name} -- $server{comment}\n//\n\n";

  # bind globals
  if (@{$server{bind_globals}} > 1) {
      print BINDFILE "// Global BIND directives\n";
      for $j (1..$#{$server{bind_globals}}) {
	  ($val=$server{bind_globals}[$j][1]) =~ s/\;\s*$//;
	  $val .= ";" unless ($val =~ /[\;\{]\s*$/);
	  print BINDFILE "$val\n";
      }
      print BINDFILE "\n";
  } 


  unless ($zones_only) {
    
    # key & acl section
    print_keys_acls($server{named_flags_ac} ? 
		    $server{masterserver} : 
		    $serverid);

    # options section
    print BINDFILE "options {\n";
    print BINDFILE "\tdirectory \"$server{'directory'}\";\n"
      if ($server{'directory'} ne '');
    print BINDFILE "\tpid-file \"$server{'pid_file'}\";\n"
      if ($server{'pid_file'} ne '');
    print BINDFILE "\tdump-file \"$server{'dump_file'}\";\n"
      if ($server{'dump_file'} ne '');
    if ($dhcp2_mode) {
      print BINDFILE "\tstats-file \"$server{'stats_file'}\";\n"
	if ($server{'stats_file'} ne '');
    } else {
      print BINDFILE "\tstatistics-file \"$server{'stats_file'}\";\n"
	if ($server{'stats_file'} ne '');
      print BINDFILE "\tmemstatistics-file \"$server{'memstats_file'}\";\n"
	if ($server{'memstats_file'} ne '');
    }
    print BINDFILE "\tnamed-xfer \"$server{'named_xfer'}\";\n"
      if ($server{'named_xfer'} ne '');

    ($val=$server{version}) =~ s/\"//g;
    print BINDFILE "\tversion \"$val\";\n" if ($val);

    for $j (0..$#bind_options) {
      $val=$yes_no_enum{$server{$bind_options[$j][0]}};
      print BINDFILE "\t$bind_options[$j][1] $val;\n" if ($val);
    }

    $val=$check_names_enum{$server{checknames_m}};
    print BINDFILE "\tcheck-names master $val;\n" if ($val ne '');
    $val=$check_names_enum{$server{checknames_s}};
    print BINDFILE "\tcheck-names slave $val;\n" if ($val ne '');
    $val=$check_names_enum{$server{checknames_r}};
    print BINDFILE "\tcheck-names response $val;\n" if ($val ne '');

    print BINDFILE "\ttransfer-source $server{transfer_source};\n" if $server{transfer_source} ne '';
    print BINDFILE "\ttransfer-source-v6 $server{transfer_source_v6};\n" if  $server{transfer_source_v6} ne '';

    if ($server{query_src_ip} || $server{query_src_port} > 0) {
      $val="address " .
           (is_cidr($server{query_src_ip}) ? $server{query_src_ip} : '*') .
           " port " .
	   ($server{query_src_port} > 0 ? $server{query_src_port} : '*');
      
      print BINDFILE "\tquery-source $val;\n";
    }

    if ($server{query_src_ip_v6} || $server{query_src_port_v6} > 0) {
      $val="address " .
           (is_cidr($server{query_src_ip_v6}) ? $server{query_src_ip_v6} : '*') .
           " port " .
	   ($server{query_src_port_v6} > 0 ? $server{query_src_port_v6} : '*');
      
        print BINDFILE "\tquery-source-v6 $val;\n";
    }

    print BINDFILE "\n";
    $tmpserverid=($server{named_flags_ac} ? $server{masterserver} : $serverid);
    print_cidr_list('allow-transfer',1,$tmpserverid,"\t");
    print_cidr_list('allow-query',7,$tmpserverid,"\t");
    print_cidr_list('allow-query-cache',14,$tmpserverid,"\t");
    print_cidr_list('allow-recursion',8,$tmpserverid,"\t");
    print_cidr_list('allow-notify',15,$tmpserverid,"\t");
    print_cidr_list('blackhole',9,$tmpserverid,"\t");

    print_cidr_list("listen-on ".
  	    ($server{listen_on_port}?"port $server{listen_on_port}":""),
	    10,$serverid,"\t", 4);

    print_cidr_list("listen-on-v6 ".
  	    ($server{listen_on_port_v6}?"port $server{listen_on_port_v6}":""),
	    10,$serverid,"\t", 6);

    print BINDFILE "\tforward $forward_enum{$server{forward}};\n"
      if ($forward_enum{$server{forward}});
    print_cidr_list('forwarders',11,$serverid,"\t");

    if (@{$server{custom_opts}} > 1) {
      print BINDFILE "\t// custom BIND options\n";
      for $j (1..$#{$server{custom_opts}}) {
	($val=$server{custom_opts}[$j][1]) =~ s/\;\s*$//;
	$val .= ";" unless ($val =~ /[\;\{]\s*$/);
	print BINDFILE "\t$val\n";
      }
      print BINDFILE "\n";
    }

    print BINDFILE "};\n";

    # logging section
    if (@{$server{logging}} > 1) {
      print BINDFILE "\nlogging {\n";
      for $j (1..$#{$server{logging}}) {
	($val=$server{logging}[$j][1]) =~ s/\;\s*$//;
	$val .= ";" unless ($val =~ /[\;\{]\s*$/);
	print BINDFILE "\t$val\n";
      }
      print BINDFILE "};\n";
    }
    print BINDFILE "\n";

  }


  # zones section
  ###############################################

  # named.root/named.ca
  unless ($server{no_roots} eq 't') {
    $zfile="$server{named_ca}";
    print BINDFILE "\n\nzone \".\" in {\n";
    print BINDFILE "\ttype hint;\n";
    print BINDFILE "\tfile \"$zfile\";\n";
    print BINDFILE "};\n\n\n";

    undef @q;
    db_query("SELECT domain,ttl,type,value FROM root_servers " .
	     "WHERE server=$serverid ORDER BY domain,type,value;",\@q);
    error(db_errormsg()) if (db_errormsg());
    unless (@q > 0) {
      undef @q;
      db_query("SELECT domain,ttl,type,value FROM root_servers " .
	       "WHERE server=-1 ORDER BY domain,type,value;",\@q);
    }
    fatal("empty $server{named_ca}") if (@q < 1);
    print "Generating $server{named_ca} file...\n";
    open(ZONEFILE, ">$zfile$tmp_extension")
      || fatal("Cannot create file: $zfile$tmp_extension!");
    $open_tmpfiles{$zfile . $tmp_extension}=$zfile;
    $time_now=localtime;
    print ZONEFILE "; $server{named_ca} - automagically generated by " .
                   "Sauron v$VER\n";
    print ZONEFILE "; created by: $user  $time_now\n";
    print ZONEFILE ";\n;\n";
    for $i (0..$#q) {
      printf ZONEFILE "%-30s  %8d  %-4s  %s\n",
	      $q[$i][0],$q[$i][1],$q[$i][2],$q[$i][3];
    }
    print ZONEFILE ";\n;\n";
    close(ZONEFILE);
  }
}

  if ($tiny_conf) {
    # @ file
    ###############################################

    unless ($server{no_roots} eq 't') {
      undef @q;
      db_query("SELECT value,domain FROM root_servers " .
	       "WHERE server=-1 AND type = 'A' ORDER BY value;",\@q);
      fatal("empty $server{named_ca}") if (@q < 1);
      print "Generating $root_file file...\n";
      open(ROOTFILE, ">$root_file$tmp_extension")
	|| fatal("Cannot create file: $root_file$tmp_extension!");
      $open_tmpfiles{$root_file . $tmp_extension}=$root_file;
      $time_now=localtime;
      print ROOTFILE "# $root_file - automagically generated by " .
	             "Sauron v$VER\n";
      print ROOTFILE "# created by: $user  $time_now\n";
      print ROOTFILE "#\n#\n";
      for $i (0..$#q) {
	printf ROOTFILE "%-30s  # %s\n",$q[$i][0],$q[$i][1];
      }
      print ROOTFILE "#\n#\n";
    close(ROOTFILE);
    }

    open(TINYDNSFILE,">$tiny_file$tmp_extension")
      || fatal("Cannot create $tiny_file$tmp_extension!");
    $open_tmpfiles{$tiny_file . $tmp_extension}=$tiny_file; 
  }

  for($loopcounter=0; $loopcounter < $zones; $loopcounter++) {
    $zonename=$$zonelist[$loopcounter][0];
    $zonename =~ s/\.$//g;
    $origin = $zonename . '.';
    $zoneid=$$zonelist[$loopcounter][1];
    #print "zone: $zonename id=$zoneid\n";
    fatal("Cannot get zone record: $zonename (id=$zoneid)!")
      if (get_zone($zoneid,\%zone));

    $type=$zone_type_enum{$zone{type}};
    fatal("uknown zone type '$zone{type}'!") unless ($type ne '');
    if ($loopcounter >= $normalzones) {
      $subtype=$type;
      # process zones from master server
      if ($processed_zones{$zonename}) {
	error("Zone from master server skipped: $zonename");
	next;
      }
      #print "slavezone: $zonename $type\n";
      if ($type eq 'master') {
	$type='slave';
      }
    }
    $processed_zones{$zonename}++;

if ($bind_conf) {
    if ($type eq 'master') {
      $zfile=$server{'pzone_path'};
      $zfile=expand_zonefile_path($zfile,$zonename);
    }
    elsif ($type eq 'slave') {
      $zfile=$server{'szone_path'};
      $zfile=expand_zonefile_path($zfile,$zonename);
    }
    else { $zfile=''; }
    $zfile.=$zonename . ".zone";

    print BINDFILE "zone \"$zonename\" $zone{class} {\n";
    print BINDFILE "\ttype $type;\n";
    $val=$check_names_enum{$zone{chknames}};
    print BINDFILE "\tcheck-names $val;\n" if ($val ne '');
    $val=$yes_no_enum{$zone{nnotify}};
    print BINDFILE "\tnotify $val;\n" if ($val ne '');
    print BINDFILE "\tfile \"$zfile\";\n"
      if ($type eq 'master' || $type eq 'slave');
    print BINDFILE "\tforward $forward_enum{$zone{forward}};\n"
      if ($forward_enum{$zone{forward}} && $type eq 'forward');

    $zone{transfer_src} =~ s/\/32$//;
    $transfer_source = $zone{transfer_src};
    $zone{transfer_src_v6} =~ s/\/128$//;
    $transfer_source6 = $zone{transfer_src_v6};
    
    print BINDFILE "\ttransfer-source $transfer_source;\n" if ($transfer_source ne '' && $loopcounter < $normalzones);
    print BINDFILE "\ttransfer-source-v6 $transfer_source6;\n" if ($transfer_source6 ne '' && $loopcounter < $normalzones);

    # allow_update
    if (($type eq 'slave') && ($loopcounter >= $normalzones) && 
	($subtype eq 'master')) {
      # omit allow-update for this zone in slave server configuration
    } else {
      print_cidr_list('allow-update',2,$zoneid,"\t");
    }
    # allow_query
    print_cidr_list('allow-query',4,$zoneid,"\t");
    # allow_transfer
    print_cidr_list('allow-transfer',5,$zoneid,"\t");
    # also_notify
    print_cidr_list('also-notify',6,$zoneid,"\t");
    # forwarders
    print_cidr_list('forwarders',12,$zoneid,"\t") if ($type eq 'forward');

    # masters (IPs)
    if ($type eq 'slave') {
      undef @q;
      if (($loopcounter >= $normalzones) && ($subtype eq 'master')) {
	@q=@master_server_ips;
      } else {
	db_query("SELECT ip FROM cidr_entries WHERE type=3 AND ref=$zoneid " .
		 "ORDER BY ip;",\@q);
	error(db_errormsg()) if (db_errormsg());
      }
      if (@q > 0) {
	print BINDFILE "\tmasters { ";
	for $j (0..(@q-1)) {
	  $ip=$q[$j][0];
	  $ip =~ s/\/32$//g;
	  $ip =~ s/\/128$//g;
	  print BINDFILE "\n\t\t\t\t$ip; ";
	}
	print BINDFILE "\n\t};\n";
      } else {
	fatal("no masters defined for slave zone: $zonename");
      }
    }

    $zone{transfer_source} =~ s/\/\d{1,3}//;
    $transfer_source = $zone{transfer_source};
    $zone{transfer_source_v6} =~ s/\/\d{1,3}//;
    $transfer_source6 = $zone{transfer_source_v6};


    print BINDFILE "\ttransfer-source $transfer_source;\n" if $transfer_source ne '';
    print BINDFILE "\ttransfer-source-v6 $transfer_source6;\n" if $transfer_source6 ne '';

    print BINDFILE "};\n\n\n";
}

    next unless ($type eq 'master');

    if ($zone{'dummy'} eq 't') {
      print "Skipping zone creation for dummy zone: $zonename\n";
      next;
    }


    ######################
    # .zone files
    if ($bind_conf) {
      print "Generating zone file: $zfile\n";
      open(ZONEFILE, ">$zfile$tmp_extension")
	|| fatal("Cannot create file: $zfile$tmp_extension!");
      $open_tmpfiles{$zfile . $tmp_extension}=$zfile;
      $time_now=localtime;
      print ZONEFILE "; zone $zonename - automagically generated by " .
	             "Sauron v$VER\n";
      print ZONEFILE "; =====" . '=' x length($zonename) . "\n;\n";
      print ZONEFILE "; created by: $user\@$host  $time_now\n";
      print ZONEFILE ";\n;\n";
    }

    $rev=$zone{'reverse'};
    $class="\U$zone{'class'}";
    $class=~s/\s+$//g;
    $TTL=$server{'ttl'};
    $TTL=$zone{'ttl'} if ($zone{ttl} > 0);
    $ttl='';
    $ttl_refresh = ($zone{refresh} ? $zone{refresh} : $server{refresh});
    $ttl_retry = ($zone{retry} ? $zone{retry} : $server{retry});
    $ttl_expire = ($zone{expire} ? $zone{expire} : $server{expire});
    $ttl_minimum = ($zone{minimum} ? $zone{minimum} : $server{minimum});

    undef @q;
    db_query("SELECT MAX(h.mdate),MAX(h.cdate) " .
             "FROM hosts h WHERE h.zone=$zoneid;",\@q);
    $hosts_mdate = ($q[0][0] > 0 ? $q[0][0] : 0);
    $hosts_mdate =  $q[0][1] if ($q[0][1] > $hosts_mdate);
    if ($rev eq 't') {
      db_query("SELECT MAX(h.cdate),MAX(h.mdate),MAX(z.rdate) " .
	       "FROM zones z JOIN hosts h ON h.zone=z.id " .
	       " JOIN a_entries a ON a.host=h.id " .
	       "WHERE z.server=$serverid AND a.ip << '$zone{reversenet}'",\@q);
      $hosts_mdate = $q[0][0] if ($q[0][0] > $hosts_mdate);
      $hosts_mdate = $q[0][1] if ($q[0][1] > $hosts_mdate);
      $hosts_mdate = $q[0][2] if ($q[0][2] > $hosts_mdate);
    }

    $serial=$zone{serial};
    $serial_date=($zone{serial_date} > 0 ? $zone{serial_date} : 0);
    if ( ($server{mdate} > $serial_date) ||
	 ($server{cdate} > $serial_date) ||
	 ($zone{mdate} > $serial_date) ||
	 ($zone{cdate} > $serial_date) ||
	 ($zone{rdate} > $serial_date) ||
	 ($hosts_mdate > $serial_date) || $opt_updateserial ) {
      #print "server: $server{mdate}, zone: $zone{mdate} ",
      #	    "hosts: $hosts_mdate  - $serial_date\n";
      $serial=new_serial($serial);
      $serial_date=time;
      unless ($opt_noupdateserial) {
	print "Updating serial $zone{serial} --> $serial\n";
	fatal("Cannot update zone $zonename serial!")
	  if (update_record('zones',{id=>$zoneid,serial=>$serial,
				     serial_date=>$serial_date}) < 0);
      }
    }
    $hostname=$server{'hostname'};
    $hostmaster=$zone{'hostmaster'};
    $hostmaster=$server{'hostmaster'} if ($hostmaster eq '');
    fatal("no hostmaster defined either in server nor zone record for: " .
	  "$zonename") if ($hostmaster eq '');
    fatal("server hostname not defined!") if ($hostname eq '');
    fatal("no TTL defined!") unless ($TTL > 0);
    if ($bind_conf) {
      print ZONEFILE '$TTL ' . "$TTL\t; default TTL for this zone\n;\n";
      print ZONEFILE '$ORIGIN ' . "$zonename.\n;\n";

      print ZONEFILE "@\t$ttl\t$class\tSOA\t$hostname $hostmaster (\n";
      print ZONEFILE "\t\t\t\t\t $serial\t; serial number\n" .
                     "\t\t\t\t\t $ttl_refresh\t\t; refresh\n" .
		     "\t\t\t\t\t $ttl_retry\t\t; retry\n" .
                     "\t\t\t\t\t $ttl_expire\t\t; expire\n" .
		     "\t\t\t\t\t $ttl_minimum\t\t; minimum\n\t\t\t\t\t)\n";

      print ZONEFILE ";\n; zone nameservers\n;\n";
    }
    if ($tiny_conf) {
      printf TINYDNSFILE "#\n";
      printf TINYDNSFILE "# $zonename\n";
      printf TINYDNSFILE "#\n";
      printf TINYDNSFILE "Z$zonename\:$hostname\:$hostmaster\:$serial\:" .
	        "$ttl_refresh\:$ttl_retry\:$ttl_expire\:$ttl_minimum\:\:\n";
    }

    foreach $i (1..$#{$zone{ns}}) {
      printf ZONEFILE $ZFORMAT, '',$ttl,$class,'NS',$zone{ns}[$i][1] 
	if ($bind_conf);
      printf TINYDNSFILE "&$zonename\:\:$zone{ns}[$i][1]\:$ttl\:\:\n" 
	if ($tiny_conf);
    }

    if ($rev eq 'f') {
      print ZONEFILE ";\n; zone mail exchanges\n;\n" 
	if ($bind_conf && @{$zone{mx}} > 1);
      for $i (1..$#{$zone{mx}}) {
	($val=$zone{mx}[$i][2]) =~ s/\$DOMAIN/$zonename./g;
	$pri = $zone{mx}[$i][1];
	printf ZONEFILE $ZFORMAT, '',$ttl,$class,'MX', "$pri $val"
	  if ($bind_conf);
	$dest = $val;
	$dest .= ".$zonename." unless ($dest =~ /\.$/);
        printf TINYDNSFILE "\@$zonename\:\:$dest\:$pri\:$ttl\:\:\n" 
	  if ($tiny_conf);
      }
      print ZONEFILE ";\n; contact info (server)\n;\n" 
	if ($bind_conf && @{$server{txt}}>1);
      foreach $i (1..$#{$server{txt}}) {
	($val=$server{txt}[$i][1]) =~ s/\"//g;
	printf ZONEFILE $ZFORMAT, '',$ttl,$class,'TXT',"\"$val\"" 
	  if ($bind_conf);
	($val=$zone{txt}[$i][1]) =~ s/\"//g;
	($val=$zone{txt}[$i][1]) =~ s/:/\\072/g;
	printf TINYDNSFILE "'$zonename\:$val\:$ttl\:\:\n" if ($tiny_conf);
      }
      print ZONEFILE ";\n; contact info (zone)\n;\n" 
	if ($bind_conf && @{$zone{txt}} > 1);
      foreach $i (1..$#{$zone{txt}}) {
	($val=$zone{txt}[$i][1]) =~ s/\"//g;
	printf ZONEFILE $ZFORMAT, '',$ttl,$class,'TXT',"\"$val\"" 
	  if ($bind_conf);
	($val=$zone{txt}[$i][1]) =~ s/\"//g;
	($val=$zone{txt}[$i][1]) =~ s/:/\\072/g;
	printf TINYDNSFILE "'$zonename\:$val\:$ttl\:\:\n" if ($tiny_conf);
      }

      # zone A records
      if (@{$zone{ip}} > 1) {
	print ZONEFILE ";\n; zone A record(s)\n;\n" if ($bind_conf);
	
    for $i (1..$#{$zone{ip}}) {
	  next unless ($zone{ip}[$i][3] =~ /^(t|true|1)$/);
	  ($ip=$zone{ip}[$i][1]) =~ s/\/32$//;
	  $ip =~ s/\/128$//;

	  $ttl=''; # fixme
	  my $recA = (ip_is_ipv4($ip) ? 'A' : 'AAAA');
      printf ZONEFILE $ZFORMAT, '',$ttl,$class,$recA,$ip if ($bind_conf);
	  printf TINYDNSFILE "+$zonename\:$ip\:$ttl\:\:\n" if ($tiny_conf);
	}
      }
    }

    # delegated zones
    undef @cnamedelegs;
    undef %delegnets;
    undef @q;
    $delegation_mask='';
    db_query("SELECT a.domain,c.ns,a.ttl FROM hosts a,zones b,ns_entries c " .
	     "WHERE c.ref=a.id AND a.zone=b.id AND c.type=2 AND a.type=2 " .
	     "AND b.id=$zoneid " .
	     "ORDER BY a.domain,c.ns;",\@q);
    error(db_errormsg()) if (db_errormsg());
    if (@q > 0) {
      print ZONEFILE ";\n;\n; delegated zones\n;\n" if ($bind_conf);
      $domain='';
      for $i (0 .. $#q) {
	print ZONEFILE ";\n" 
	  if ($bind_conf && ($domain ne '') && ($domain ne $q[$i][0]));
	$val = ($domain ne $q[$i][0] ? $q[$i][0] : '');
	if ($rev eq 't') {
	  if ($val =~ /^(\d+)-(\d+)(\.(\S+))?$/) {
	    push @cnamedelegs, [$1,$2,$4,$val];
	    if ($4) {
	      $dnet = arpa2cidr("$4.$zonename");
	      fatal("invalid reverse delegation '$val'")
		if ($dnet eq '0.0.0.0/0' || $dnet eq '');
	    }
	    print "special (CNAME hack) delegation: $val\n" if ($opt_verbose);
	  } elsif ($val) {
	    $dnet = arpa2cidr("$val.$zonename");
	    fatal("invalid reverse delegation '$val'")
	      if ($dnet eq '0.0.0.0/0' || $dnet eq '');
	    #print "dnet: $dnet\n";
	    $net = new Net::Netmask($dnet);
	    $net->storeNetblock(\%delegnets);
	  }
	}
	$domain=$q[$i][0];
	$ns=$q[$i][1];
	$ttl=$q[$i][2];
	$ttl='' unless ($ttl > 0);
	if ($val) {
	  $delegation_mask .= '|' if ($delegation_mask);
	  $delegation_mask .= $val;
	}
	printf ZONEFILE $ZFORMAT, $val,$ttl,$class,'NS',$ns if ($bind_conf);
	# Make sure ns is a fqdn.
	$ns .= ".$zonename." unless ($ns =~ /\.$/);
	$src = "$domain";
	$src .= ".$zonename" unless ($src =~ /\.$/); 
	printf TINYDNSFILE "&$src\:\:$ns\:$ttl\:\:\n" if ($tiny_conf);
      }
	print ZONEFILE ";\n" if ($bind_conf);
    }

    $delegation_mask =~ s/\./\\\./g;
    #print "dmask='$delegation_mask'\n";

    if ($rev eq 't') {
      # reverse zone

      # generate reverse map for master reverse zones

      $rorigin = cidr2arpa($zone{'reversenet'}) . ".";

      undef %riphash;
      undef @q;
      db_query("SELECT a.ip,h.domain,h.ttl,z.name,h.expiration " .
	       "FROM hosts h,zones z,a_entries a " .
	       "WHERE z.server=$serverid AND h.zone=z.id " .
	       " AND (h.type=1 OR h.type=6 OR h.type=10) " .
	       " AND a.host=h.id " .
	       " AND a.reverse=true AND a.ip << '$zone{reversenet}' " .
	       "ORDER BY a.ip,h.domain",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
       print ZONEFILE ";\n; reverse map for: $zone{reversenet}\n;\n"
	  if ($bind_conf);
	for $i (0 .. $#q) {
	  if (findNetblock($q[$i][0],\%delegnets)) {
	    #print "SKIP: $q[$i][0]\n";
	    next;
	  }
	  if (($q[$i][4] > 0) && ($q[$i][4] < $time_now_ticks)) {
	    #print "Skipping expired host reverse: $q[$i][0]\n";
	    next;
	  }
	  $rip=remove_origin(cidr2arpa($q[$i][0]).".",$rorigin);
	  $rzone_name=$q[$i][3];
	  $ttl=$q[$i][2];
	  $ttl='' unless ($ttl >0);
	  $rdomain=$q[$i][1];
	  if ($rdomain eq '@') {
	    $rdomain = "$rzone_name.";
	  } else {
	    $rdomain .= ".$rzone_name." unless ($rdomain =~ /\.$/);
	  }
	  printf ZONEFILE $ZFORMAT, $rip,$ttl,$class,'PTR',$rdomain
	    if ($bind_conf);
	  printf TINYDNSFILE "^$rip\.$rorigin\:$rdomain\:$ttl\:\:\n"
	    if ($tiny_conf);
	  $riphash{$rip}=$rdomain;
	}
      }


      # generate CNAMEs for delegations for small nets

      if (@cnamedelegs > 0) {
	print ZONEFILE ";\n; CNAME maps for special delegations\n"
	  if ($bind_conf);
	for $i (0..$#cnamedelegs) {
	  $start=$cnamedelegs[$i][0];
	  $end=$cnamedelegs[$i][1];
	  $rest=$cnamedelegs[$i][2];
	  $rest='.'.$rest if ($rest);
	  $domain=$cnamedelegs[$i][3];
	  print ZONEFILE ";\n; $domain:\n" if ($bind_conf);
	  for $j ($start..$end) {
	    $rip = $j.$rest;
	    unless ($riphash{$rip}) {
	      printf ZONEFILE $ZFORMAT, $rip,'',$class,'CNAME',
		     "$j.$domain.$zonename." if ($bind_conf);
	      printf TINYDNSFILE "C$rip\:$j.$domain.$zonename\.\:$ttl\:\:\n"
		if ($tiny_conf);
	    } else {
	      printf ZONEFILE "; $rip ($riphash{$rip})\n" if ($bind_conf);
	    }
	  }
	  print ZONEFILE ";\n" if ($bind_conf);
	}
	print ZONEFILE ";\n" if ($bind_conf);
      }

    } else {
      # normal zone

      # glue records needed for delgated zones
      undef @q;
      db_query("SELECT hosts.domain,a_entries.ip,hosts.ttl " .
	       "FROM hosts,zones,a_entries " .
	       "WHERE hosts.zone=zones.id AND hosts.id=a_entries.host " .
	       "AND zones.id=$zoneid AND hosts.type=6 " .
	       "ORDER BY hosts.domain;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
      %dnames = undef;
	print ZONEFILE ";\n; Glue A records\n;\n" if ($bind_conf);
	for $i (0 .. $#q) {
	  $domain=$q[$i][0];
	  $ip=$q[$i][1];
	  $ip =~ s/\/\d{1,3}$//g;
	  $ttl=$q[$i][2];
	  $ttl='' unless ($ttl > 0);
      my $recA = (ip_is_ipv4($ip) ? 'A' : 'AAAA');
	  $dname = ($dname{$domain} ? '' : $domain);
      printf ZONEFILE $ZFORMAT, $dname,$ttl,$class,$recA,$ip
	    if ($bind_conf);
	  printf TINYDNSFILE "+$dname\.$zonename\:$ip\:$ttl\:\:\n"
	    if ($tiny_conf);
      $dname{$domain}++; 
	}
      }

      # plain MX domains

      undef @q; # entries with mx-templates
      db_query("SELECT h.domain,m.pri,m.mx,h.ttl " .
	       "FROM hosts h,zones z,mx_entries m,mx_templates r " .
	       "WHERE h.zone=z.id AND h.mx=r.id AND m.ref=r.id AND m.type=3 " .
	       " AND h.type=3 AND h.mx > 0 AND z.id=$zoneid " .
	       "ORDER BY h.domain,m.pri,m.mx;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	print ZONEFILE ";\n;\n; MX only domains (using MX-templates)\n;\n"
	  if ($bind_conf);
	$domain='';
	for $i (0 .. $#q) {
	  print ZONEFILE ";\n" if ($bind_conf && $domain ne '' && 
				   $domain ne $q[$i][0]);
	  $val=($domain ne $q[$i][0] ? $q[$i][0] : '');
	  $domain=$q[$i][0];
	  $ttl=$q[$i][3];
	  $ttl='' unless ($ttl > 0);
	  $pri = $q[$i][1];
	  $q[$i][2] =~ s/\$DOMAIN/$domain/g;
	  printf ZONEFILE $ZFORMAT, $val,$ttl,$class,'MX',
	                  "$pri $q[$i][2]" if ($bind_conf);
          $src = $domain;
          $src .= ".$zonename" unless ($src =~ /\.$/);
	  $dest = $q[$i][2];
	  $dest .= ".$zonename." unless ($dest =~ /\.$/);
          printf TINYDNSFILE "\@$src\:\:$dest\:$pri\:$ttl\:\:\n"
	    if ($tiny_conf);
	  
	}
	print ZONEFILE ";\n" if ($bind_conf);
      }
      undef @q; # entries without mx-templates
      db_query("SELECT h.domain,m.pri,m.mx,h.ttl " .
	       "FROM hosts h,zones z,mx_entries m " .
	       "WHERE h.zone=z.id AND m.ref=h.id AND m.type=2 " .
	       " AND h.type=3 AND h.mx=-1 AND z.id=$zoneid " .
	       "ORDER BY h.domain,m.pri,m.mx;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	print ZONEFILE ";\n;\n; MX only domains\n;\n" if ($bind_conf);
	$domain='';
	for $i (0 .. $#q) {
	  print ZONEFILE ";\n" if ($bind_conf && $domain ne '' && 
				   $domain ne $q[$i][0]);
	  $val=($domain ne $q[$i][0] ? $q[$i][0] : '');
	  $domain=$q[$i][0];
	  $ttl=$q[$i][3];
	  $ttl='' unless ($ttl > 0);
	  $q[$i][2] =~ s/\$DOMAIN/$domain/g;
	  printf ZONEFILE $ZFORMAT, $val,$ttl,$class,'MX',
	                  "$q[$i][1] $q[$i][2]" if ($bind_conf);
	  printf TINYDNSFILE "\@$val\.$zonename\:\:$q[$i][2]\:$q[$i][1]\:" .
	                     "$ttl\:\:\n" if ($bind_conf);
	}
	print ZONEFILE ";\n" if ($bind_conf);
      }



      ## wks hash
      undef %wkshash;
      undef @q;
      # wks templates
      db_query("SELECT h.domain,w.proto,w.services FROM hosts h,zones z, " .
	             "wks_templates r, wks_entries w " .
	       "WHERE h.zone=z.id AND h.wks=r.id AND w.ref=r.id " .
	       "AND w.type=2 AND z.id=$zoneid AND h.type=1 " .
	       "ORDER BY h.domain, w.proto;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	for $i (0 .. $#q) {
	  $rec=$wkshash{$q[$i][0]};
	  if (!$rec) {
	    $rec=[]; 
	    $wkshash{$q[$i][0]}=$rec;
	  }
	  $proto=$q[$i][1];
	  $proto=~s/\s+$//g;
	  push @{$wkshash{$q[$i][0]}}, "$proto $q[$i][2]";
	}
      }
      undef %wkshash2;
      undef @q;
      # wks entries (host specific)
      db_query("SELECT h.domain,w.proto,w.services " .
	       "FROM hosts h,zones z,wks_entries w " .
	       "WHERE z.id=$zoneid AND h.zone=z.id AND h.type=1 " .
	       " AND w.type=1 AND w.ref=h.id " .
	       "ORDER BY h.domain,w.proto,w.services;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	for $i (0 .. $#q) {
	  $rec=$wkshash2{$q[$i][0]};
	  if (!$rec) {
	    $rec=[]; 
	    $wkshash2{$q[$i][0]}=$rec;
	  }
	  $proto=$q[$i][1];
	  $proto=~s/\s+$//g;
	  push @{$wkshash2{$q[$i][0]}}, "$proto $q[$i][2]";
	}
      }



      ## mx hash (mx templates)
      undef %mxhash;
      undef @q;
      db_query("SELECT r.id,m.pri,m.mx " .
	       "FROM zones z,mx_templates r, mx_entries m " .
	       "WHERE z.id=$zoneid AND r.zone=z.id " .
	       "AND m.ref=r.id AND m.type=3 " .
	       "ORDER BY r.id,m.pri,m.mx;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	for $i (0 .. $#q) {
	  $rec=$mxhash{$q[$i][0]};
	  if (!$rec) {
	    $rec=[];
	    $mxhash{$q[$i][0]}=$rec;
	  }
	  push @{$mxhash{$q[$i][0]}}, "$q[$i][1] $q[$i][2]";
	}
      }
      undef %mxhash2;
      undef @q;
      # mx entries (host specific)
      db_query("SELECT h.domain,m.pri,m.mx " .
	       "FROM hosts h,zones z,mx_entries m " .
	       "WHERE z.id=$zoneid AND h.zone=z.id AND h.type=1 " .
	       " AND m.type=2 AND m.ref=h.id " .
	       "ORDER BY h.domain,m.pri,m.mx;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	for $i (0 .. $#q) {
	  $rec=$mxhash2{$q[$i][0]};
	  if (!$rec) {
	    $rec=[]; 
	    $mxhash2{$q[$i][0]}=$rec;
	  }
	  push @{$mxhash2{$q[$i][0]}}, "$q[$i][1] $q[$i][2]";
	}
      }


      ## txt hash
      undef %txthash;
      undef @q;
      db_query("SELECT h.domain,t.txt FROM hosts h,zones z,txt_entries t " .
	       "WHERE h.zone=z.id AND t.type=2 AND t.ref=h.id " .
	       "AND h.type=1 AND z.id=$zoneid " .
	       "ORDER BY h.domain;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	for $i (0 .. $#q) {
	  $rec=$txthash{$q[$i][0]};
	  if (!$rec) {
	    $rec=[];
	    $txthash{$q[$i][0]}=$rec;
	  }
	  $val=$q[$i][1];
	  $val =~ s/\"//g;
	  push @{$txthash{$q[$i][0]}}, $val;
	}
      }


      # normal host entries
      undef @q;
      #undef %dnames;
      my %dnscnts;   
      my $dns_index = 1; 

      my $ign_local = ($opt_ign_local ? "AND NOT (a.ip << '10.0.0.0/8') AND NOT (a.ip << '172.16.0.0/12') AND NOT (a.ip << '192.168.0.0/16')" : ""); 

      db_query("SELECT h.domain,a.ip,h.hinfo_hw,h.hinfo_sw,h.ttl,h.huser, " .
	       " h.dept,h.location,h.info,h.mx,h.expiration " .
	       "FROM hosts h, a_entries a, zones z " .
	       "WHERE h.zone=z.id AND a.host=h.id " .
	       "AND z.id=$zoneid AND h.type=1 " .
	       "AND a.forward=true AND h.domain <> '\@' $ign_local " .
	       #"ORDER BY a.ip,h.domain;",\@q);
	       "ORDER BY h.domain, a.ip;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
    print ZONEFILE ";\n;\n; A and AAAA records\n;\n" if ($bind_conf);
    #Num of records for all hostnames (maybe slow for large installation)
    $dnscnts{remove_origin($$_[0],$origin)}++ foreach (@q);
    for $i (0 .. $#q) {
	  #$domain=$q[$i][0];
	  $domain=remove_origin($q[$i][0],$origin);
	  if ($domain =~ /\.$/) {
	    error("Out-of-zone host skipped: $q[$i][0]");
	    next;
	  }
	  if ($domain =~ /\.($delegation_mask)$/) {
	    error("Host in delegated zone skipped: $q[$i][0]");
	    next;
	  }
	  if (($q[$i][10] > 0) && ($q[$i][10] < $time_now_ticks)) {
	    error("Skipping expired host: $q[$i][0]") if ($opt_verbose);
	    next;
	  }
      
	  $ip=$q[$i][1];
	  $ip =~ s/\/\d+$//g;
	  $hinfo_hw=$q[$i][2];
	  $hinfo_sw=$q[$i][3];
	  $ttl=$q[$i][4];
	  $ttl='' unless ($ttl > 0);
	  $txt=$txthash{$domain};
	  $wks=$wkshash{$domain}; # FIXME (implement like mxhashes)
	  $wks=$wkshash2{$domain} if ($wkshash2{$domain});
	  $mx=$mxhash{$q[$i][9]};
	  $mx=$mxhash2{$domain} if ($mxhash2{$domain});
	  $info=$q[$i][5];
	  if ($q[$i][6]) { $info.=", " if ($info); $info.=$q[$i][6]; }
	  if ($q[$i][7]) { $info.=" " if ($info); $info.=$q[$i][7]; }
	  if ($q[$i][8]) { $info.=", " if ($info); $info.=$q[$i][8]; }
	  
      my $recA = (ip_is_ipv4($ip) ? 'A' : 'AAAA');
      
      #print "DOMAIN: $domain\n";
      #if ($dnames{$domain} and ip_get_version($dnames{$domain}) == ip_get_version($ip)) {
	  #  #print ("duplicate A record $ip for $domain ($dnames{$domain}).");
      #  printf ZONEFILE $ZFORMAT, $domain,$ttl,$class,$recA,$ip
	  #    if ($bind_conf);
	  #  printf ZONEFILE ";\n" if ($bind_conf);
	  #  printf TINYDNSFILE "+$domain\.$zonename\:$ip\:$ttl\:\n"
	  #    if ($tiny_conf);
	  #  next;
	  #}
	  #$dnames{$domain}=$ip;
	  
      #No hostname for second and further
      $dname = ($dns_index == 1 ? $domain : '');
      printf ZONEFILE $ZFORMAT, $dname,$ttl,$class,$recA,$ip
	    if ($bind_conf);
	  printf TINYDNSFILE "+$dname\.$zonename\:$ip\:$ttl\:\:\n"
	    if ($tiny_conf);
    if (!$server{named_flags_hinfo} and ($dns_index == $dnscnts{$domain})) {
    #if (!$server{named_flags_hinfo}) {
	    printf ZONEFILE $ZFORMAT, '',$ttl,$class,'HINFO',
	      "$hinfo_hw $hinfo_sw"  if ($bind_conf && $hinfo_hw ne '' &&
					 $hinfo_sw ne '');
            printf TINYDNSFILE "# HINFO $domain\.$zonename\n"
	      if ($tiny_conf && $hinfo_hw ne '' && $hinfo_sw ne '');
            # FIXME HINFO
	    # ($hinfo_host=$domain\.$zonename) =~ s/\./\\003/g;
	    # $octal_ttl = sprintf("%o",$ttl);
	    # printf TINYDNSFILE "# \:$source\:13\:$hinfo_host\\000"
	    #   if ($tiny_conf && $hinfo_hw ne '' && $hinfo_sw ne '');
	  }
	  unless ($server{named_flags_wks}) {
	    if ($wks) {
	      for $k (0 .. $#{$wks}) {
		printf ZONEFILE $ZFORMAT, '',$ttl,$class,'WKS',"$ip $$wks[$k]"
		  if ($bind_conf);
		printf TINYDNSFILE "# WKS $domain\.$zonename $ip $$wks[$k]\n"
		  if ($tiny_conf);
		# FIXME WKS
	      }
	    }
	  }
      #firstmx for eliminating duplication mx records
	  if ($mx and !$firstmx{$domain}) {
        $firstmx{$domain} = 1;
	    for $k (0 .. $#{$mx}) {
	      $val=$$mx[$k];
              $val =~ s/\$DOMAIN/$domain/g;
	      printf ZONEFILE $ZFORMAT, '',$ttl,$class,'MX',$val
		if ($bind_conf);
	      @pri_host = split / / , $val;
	      $dest = $pri_host[1];
	      $pri = $pri_host[0];
	      $dest = "$dest\.$zonename." if ( $dest =~ /.[^\.]/);
	      $src = "$zonename";
	      $src = "$domain\.$src" if ($domain ne '');
	      printf TINYDNSFILE "\@$src\:\:$dest\:$pri\:$ttl\:\:\n"
		if ($tiny_conf);
	    }
	  }
	  for $k (0 .. $#{$txt}) {
	    $val=$$txt[$k];
	    $val =~ s/\"//g;
	    printf ZONEFILE $ZFORMAT, '',$ttl,$class,'TXT',"\"$val\""
	      if ($bind_conf);
            $val=~ s/:/\\072/g;
            printf TINYDNSFILE "'$zonename\:$val\:$ttl\:\:\n" if ($tiny_conf);
	  }
	  $info =~ s/\"//g;
	  if ($zone{txt_auto_generation}) {
            if ($info ne '') {
              printf ZONEFILE $ZFORMAT, '',$ttl,$class,'TXT',"\"$info\""
	        if ($bind_conf);
              $info =~ s/:/\\072/g;
              printf TINYDNSFILE "'$zonename\:$val\:$info\:\:\n"
		if ($tiny_conf);
	    }	      
	  }
	  print ZONEFILE ";\n" if ($bind_conf and $dns_index == $dnscnts{$domain});
	  #print ZONEFILE ";\n" if ($bind_conf);
      $dns_index = ($dns_index < $dnscnts{$domain} ? ++$dns_index : 1);      
	}

      }


      # aliases (A records)
      undef @q;
      db_query("SELECT a.domain,b.domain,ae.ip,a.ttl,a.expiration," .
	       " b.expiration " .
	       "FROM hosts a, hosts b,zones z, arec_entries r,a_entries ae " .
	       "WHERE z.id=$zoneid AND a.type=7 AND a.zone=z.id " .
	       " AND r.host=a.id AND b.id=r.arec AND b.type=1 " .
	       " AND ae.host=b.id AND ae.forward=true " .
	       "ORDER BY a.domain;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	print ZONEFILE ";\n;\n; In-zone aliases (A records)\n;\n"
	  if ($bind_conf);
	for $i (0 .. $#q) {
	  #$domain=$q[$i][0];
	  $domain=remove_origin($q[$i][0],$origin);
	  if ($domain =~ /\.$/) {
	    error("Out-of-zone alias (A rec) skipped: $q[$i][0]");
	    next;
	  }
	  if ($domain =~ /\.($delegation_mask)$/) {
	    error("Alias (A rec) in delegated zone skipped: $q[$i][0]");
	    next;
	  }
	  if (($q[$i][4] > 0) && ($q[$i][4] < $time_now_ticks)) {
	    error("Skipping expired A rec alias: $q[$i][0]") if ($opt_verbose);
	    next;
	  }
	  if (($q[$i][5] > 0) && ($q[$i][5] < $time_now_ticks)) {
	    error("Skipping A rec alias to expired host: $q[$i][0]")
	      if ($opt_verbose);
	    next;
	  }
	  $alias=$q[$i][2];
	  $ttl=$q[$i][3];
	  $ttl='' unless ($ttl > 0);
	  $alias =~ s/\/\d{1,2}\s*$//g;
	  if ($bind_conf) {
            my $recA = (ip_is_ipv4($ip) ? 'A' : 'AAAA');
            printf ZONEFILE $ZFORMAT, $domain,$ttl,$class,$recA,$alias;
            printf ZONEFILE $ZFORMAT, '',$ttl,$class,'TXT',
	                    "\"IP alias for $q[$i][1]\"";
	    print ZONEFILE ";\n";
          }
	  if ($tiny_conf) {
            printf TINYDNSFILE "+$domain\.$zonename\:$alias\:$ttl\:\n";
	    ($sfor=$q[$i][1]) =~ s/:/\\072/g;
	    $text = "\"IP alias for $sfor\"";
	    printf TINYDNSFILE "'$zonename\:$text\:$ttl\:\:\n";
	  }
	}
      }

      # aliases (CNAME records)
      undef @q;
      db_query("SELECT a.domain,b.domain,a.ttl,a.expiration,b.expiration " .
	       "FROM hosts a, hosts b,zones z " .
	       "WHERE a.zone=z.id AND z.id=$zoneid " .
	       "AND a.type=4 AND a.alias=b.id AND b.type=1 " .
	       "ORDER BY a.domain;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	print ZONEFILE ";\n;\n; In-zone aliases (CNAME records)\n;\n";
	for $i (0 .. $#q) {
	  #$domain=$q[$i][0];
	  $domain=remove_origin($q[$i][0],$origin);
	  if ($domain =~ /\.$/) {
	    error("Out-of-zone alias (CNAME) skipped: $q[$i][0]");
	    next;
	  }
	  if ($domain =~ /\.($delegation_mask)$/) {
	    error("Alias (CNAME) in delegated zone skipped: $q[$i][0]");
	    next;
	  }
	  if (($q[$i][3] > 0) && ($q[$i][3] < $time_now_ticks)) {
	    error("Skipping expired CNAME alias: $q[$i][0]") if ($opt_verbose);
	    next;
	  }
	  if (($q[$i][4] > 0) && ($q[$i][4] < $time_now_ticks)) {
	    error("Skipping CNAME alias to expired host: $q[$i][0]")
	      if ($opt_verbose);
	    next;
	  }
	  $ttl=$q[$i][2];
	  $ttl='' unless ($ttl > 0);
	  $alias=$q[$i][1];
	  $alias="$alias.$origin" unless ($alias =~ /(^\@|\.)$/);
	  printf ZONEFILE $ZFORMAT, $domain,$ttl,$class,'CNAME',$alias
	    if ($bind_conf);
	  printf TINYDNSFILE "C$domain\.$zonename\:$alias\:$ttl\:\:\n"
	    if ($tiny_conf);
	}
      }
      # aliases outside zone
      undef @q;
      db_query("SELECT h.domain,h.cname_txt,h.ttl,h.expiration " .
               "FROM hosts h,zones z " .
	       "WHERE h.zone=z.id AND z.id=$zoneid " .
	       "AND h.type=4 AND h.alias=-1 " .
	       "ORDER BY h.domain;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	print ZONEFILE ";\n;\n; Outside-zone aliases (CNAME records)\n;\n"
	  if ($bind_conf);
	for $i (0 .. $#q) {
	  #$domain=$q[$i][0];
	  $domain=remove_origin($q[$i][0],$origin);
	  if ($domain =~ /\.$/) {
	    error("Out-of-zone alias (CNAME) skipped: $q[$i][0]");
	    next;
	  }
	  if ($domain =~ /\.($delegation_mask)$/) {
	    error("Alias (CNAME) in delegated zone skipped: $q[$i][0]");
	    next;
	  }
	  if (($q[$i][3] > 0) && ($q[$i][3] < $time_now_ticks)) {
	    error("Skipping expired (out of zone) CNAME alias: $q[$i][0]");
	    next;
	  }
	  $alias=$q[$i][1];
	  $ttl=$q[$i][2];
	  $ttl='' unless ($ttl > 0);
	  printf ZONEFILE $ZFORMAT, $domain,$ttl,$class,'CNAME',$alias
	    if ($bind_conf);
	  printf TINYDNSFILE "C$domain\.$zonename\:$alias\:$ttl\:\:\n"
	    if ($tiny_conf);
	}
      }


      # SRV records
      undef @q;
      db_query("SELECT a.domain,a.ttl,a.expiration," .
	       " b.pri,b.weight,b.port,b.target " .
	       "FROM hosts a, srv_entries b, zones z " .
	       "WHERE a.zone=z.id AND z.id=$zoneid " .
	       "AND a.type=8 AND a.id=b.ref " .
	       "ORDER BY a.domain,b.port,b.pri,b.weight,b.target;",\@q);
      error(db_errormsg()) if (db_errormsg());
      if (@q > 0) {
	print ZONEFILE ";\n;\n; SRV records\n;\n" if ($bind_conf);
	for $i (0 .. $#q) {
	  $domain=remove_origin($q[$i][0],$origin);
	  if ($domain =~ /\.$/) {
	    error("Out-of-zone SRV record skipped: $q[$i][0]");
	    next;
	  }
	  if ($domain =~ /\.($delegation_mask)$/) {
	    error("SRV in delegated zone skipped: $q[$i][0]");
	    next;
	  }
	  if (($q[$i][2] > 0) && ($q[$i][2] < $time_now_ticks)) {
	    error("Skipping expired SRV record: $q[$i][0]");
	    next;
	  }
	  $ttl=$q[$i][2];
	  $ttl='' unless ($ttl > 0);
	  $pri=($q[$i][3] > 0 ? $q[$i][3] : 0);
	  $weight=($q[$i][4] > 0 ? $q[$i][4] : 0);
	  $port=($q[$i][5] > 0 ? $q[$i][5] : 0);
	  $target=$q[$i][6];
	  $target.=".$origin" unless ($target =~ /\.$/);
	  printf ZONEFILE $ZFORMAT, $domain,$ttl,$class,'SRV',
	         "$pri $weight $port $target" if ($bind_conf);

	  # Service : test.com
	  # Priority: 100
	  # Weight: 200
	  # Port: 80
	  # Dest: dest.com
	  # is 
          # ":test.com:33:\000\144\000\310\000\120\004dest\003com\000"
	  
	  $source = "$zonename" if ($domain eq '');
	  $source = "$domain\.$zonename" if ($domain ne '');
	  $octal_pri = sprintf("%o",$pri);
	  $octal_weight = sprintf("%o",$weight);
	  $octal_port = sprintf("%o",$port);
	  $target =~ s/\./\\003/g;
	  
          printf TINYDNSFILE "# CHECK SRV $domain\.$zonename $ttl $class " .
	                     "$pri $weight $port $target\n" if ($tiny_conf);
          printf TINYDNSFILE "\:$source\:33\:\\000\\$octal_pri\\000\\".
	                     "$octal_weight\\000\\$octal_port\\004" .
			     "$target\\000\n" if ($tiny_conf);
	}
      }
    }

    # custom zone file entries
    if (@{$zone{zentries}} > 1) {
      print ZONEFILE ";\n; Custom entries\n;\n" if ($bind_conf);
      for $i (1..$#{$zone{zentries}}) {
	print ZONEFILE $zone{zentries}[$i][1],"\n" if ($bind_conf);
	# Nothing we can do for tinydns here I guess ..
      }
    }
    print ZONEFILE ";\n;\n; eof\n" if ($bind_conf);
    
    close(ZONEFILE) if ($bind_conf);

    # validate zone file if --check specified
    if ($opt_check && $SAURON_ZONE_CHK_PROG) {
      print "Validating zonefile $zfile...\n" if ($opt_verbose);
      @args = split(/\s+/,$SAURON_ZONE_CHK_ARGS);
      push @args, "$zonename.";
      push @args, $zfile . $tmp_extension;
      print "run: $SAURON_ZONE_CHK_PROG " .join(' ',@args)."\n"
	  if ($opt_verbose);
      $res = system($SAURON_ZONE_CHK_PROG,@args);
      fatal("$zfile validity check failed ($res)") if ($res);
    }
  }

  print BINDFILE "\n\n// eof\n" if ($bind_conf);
  close(BINDFILE) if ($bind_conf);
  close(TINYDNSFILE) if ($tiny_conf);


  # validate named.conf if --check specified
  if ($opt_check && $SAURON_NAMED_CHK_PROG && (not $zones_only)) {
    print "Validating named.conf...\n" if ($opt_verbose);
    fatal("cannot run SAURON_NAMED_CHK_PROG: $SAURON_NAMED_CHK_PROG")
      unless (-x $SAURON_NAMED_CHK_PROG);

    @args = split(/\s+/,$SAURON_NAMED_CHK_ARGS);
    push @args, $bind_filename . $tmp_extension;
    print "run: $SAURON_NAMED_CHK_PROG " .join(' ',@args)."\n" 
	if ($opt_verbose);
    $res = system($SAURON_NAMED_CHK_PROG,@args);
    fatal("named.conf validity check failed ($res)") if ($res);
  }

  # remove $tmp_extension from generated files' names
  foreach $tmpfile (keys %open_tmpfiles) {
    print "rename: $tmpfile --> $open_tmpfiles{$tmpfile}\n" if ($opt_verbose);
    fatal("failed to rename tmpfile: $tmpfile")
      unless(rename($tmpfile,$open_tmpfiles{$tmpfile})==1);
    delete $open_tmpfiles{$tmpfile};
  }

  # mail notification to users...
  mail_notifications() if ($mailnotify_mode);

  # update lastrun record in server record
  fatal("Failed to update server record (lastrun)!")
    if (update_record('servers',{id=>$serverid,lastrun=>time()}) < 0);

}


sub print_cidr_list($$$$$) {
  my($name,$type,$ref,$hdr,$family) = @_;
  my(@q,$j,$val,$com);

  $family = ($family ? "AND family(c.ip)=$family" : "");

  db_query("SELECT c.mode,c.ip,c.acl,c.tkey,c.op,c.port,c.comment" .
           ",a.name,k.name " .
	   "FROM cidr_entries c LEFT JOIN acls a ON c.acl=a.id " .
	   " LEFT JOIN keys k ON c.tkey=k.id " .
	   "WHERE c.type=$type AND c.ref=$ref $family" .
	   "ORDER BY c.id;",\@q);

    error(db_errormsg()) if (db_errormsg());
    if (@q > 0) {
      print BINDFILE "${hdr}${name} {\n";
      for $j (0..(@q-1)) {
	my ($mode,$ip,$acl,$tkey,$op,$port,$comment,$aclname,$keyname) 
	    = @{$q[$j]};
	  
	$ip =~ s/\/32\s*$//;
	$ip =~ s/\/128\s*$//;
	$com=($comment ? "  // $comment" : "");
	$op=($op == 1 ? '!' : ' ');
	$val='';

	if ($mode == 0 && $ip) { $val=$ip; }
	elsif ($mode == 1 && $aclname) { $val=$aclname; }
	elsif ($mode == 2 && $keyname) { $val="key $keyname"; }
	
	# forward zones forwarders port 
	if ($mode == 0 & $type == 12 && $port) { $val .= " port $port"; }

	printf BINDFILE "$hdr\t%1s %-20s %s\n",$op,"$val;",$com
	  if ($val);
      }
      print BINDFILE "\t};\n";
    }

}


######################################################################
# DHCP

sub make_dhcp() {
  print "DHCP configuration\n";
  $time_now = localtime;

  # dhcpd.conf
  $dhcpd_conf_filename = ($opt_dhcpclass ? "$opt_dhcpclass.class" :
			  "dhcpd.conf");
  print "Generating $dhcpd_conf_filename...\n";

  open(DHCPFILE,">$dhcpd_conf_filename$tmp_extension")
    || fatal("Cannot create $dhcpd_conf_filename$tmp_extension!");
  $open_tmpfiles{"$dhcpd_conf_filename" . $tmp_extension} =
    "$dhcpd_conf_filename";
  print DHCPFILE "# $dhcpd_conf_filename -- automagically generated ",
                 "by Sauron v$VER\n";
  print DHCPFILE "#               created by $user at $time_now\n#\n",
                 "# server: $server{name} -- $server{comment}\n#\n\n";

  $serverid=$server{masterserver} if ($server{masterserver} > 0);
  goto print_dhcp_classes if ($opt_dhcpclass);


  # failover stuff
  if ($failover_mode) {
    $foserver=($failover_mode == 1 ? \%server : \%masterserver);
    $fo_host=$server{hostname};
    $fo_port=$fo_pport=$foserver->{df_port};
    $fo_mrd=$foserver->{df_max_delay};
    $fo_muu=$foserver->{df_max_uupdates};
    $fo_mclt=$foserver->{df_mclt};
    $fo_split=$foserver->{df_split};
    $fo_lbm=$foserver->{df_loadbalmax};
    if ($failover_mode == 1) {
      undef @q;
      db_query("SELECT hostname FROM servers " .
	       "WHERE masterserver=$server{id}",\@q);
      error("cannot find secondary failover peer!") unless ($q[0][0]);
      $fo_peer=$q[0][0];
    } else {
      $fo_peer=$foserver->{hostname};
    }
    $fo_host =~ s/\.\s*$//;
    $fo_peer =~ s/\.\s*$//;


    print DHCPFILE "failover peer \"$failover_peer_name\" {\n",
                   "\t".($failover_mode == 1 ? 'primary':'secondary').";\n",
		   "\taddress $fo_host;\n",
		   "\tport $fo_port;\n",
		   "\tpeer address $fo_peer;\n",
		   "\tpeer port $fo_pport;\n",
		   "\tmax-response-delay $fo_mrd;\n",
		   "\tmax-unacked-updates $fo_muu;\n",
		   "\tmclt $fo_mclt;\n",
		   ($failover_mode == 1 ? "\tsplit $fo_split;\n" : ''),
		   "\tload balance max seconds $fo_lbm;\n}\n\n";
  }

  # ether hash
  undef @q;
  db_query("SELECT h.ether,a.ip FROM hosts h,zones z,a_entries a " .
	   "WHERE z.server=$serverid AND h.zone=z.id AND a.host=h.id " .
	   " AND h.ether NOTNULL AND family(a.ip) = 4;",\@q);
  error(db_errormsg()) if (db_errormsg());
  for $i (0..$#q) {
    unless ($q[$i][0] =~ /^[0-9A-F]{12}$/) {
      error("Invalid ethernet address ($q[$i][0]) skipped");
      next;
    }
    $etherhash{$q[$i][0]}=[] unless ($etherhash{$q[$i][0]});
    push @{$etherhash{$q[$i][0]}}, $q[$i][1];
  }
  # add ether_aliases to ether hash too
  undef @q;
  db_query("SELECT b.ether,c.ip FROM hosts a, hosts b, zones z, a_entries c ".
	   "WHERE z.server=$serverid AND a.zone=z.id AND a.ether_alias=b.id ".
	   " AND c.host=a.id AND family(c.ip) = 4;",\@q);
  error(db_errormsg()) if (db_errormsg());
  for $i (0..$#q) {
    unless ($q[$i][0] =~ /^[0-9A-F]{12}$/) {
      error("Invalid ethernet address ($q[$i][0]) skipped (ether_alias)");
      next;
    }
    $etherhash{$q[$i][0]}=[] unless ($etherhash{$q[$i][0]});
    push @{$etherhash{$q[$i][0]}}, $q[$i][1];
  }


  # host dhcp hash
  undef @q;
  db_query("SELECT h.id,d.dhcp,d.comment FROM hosts h,zones z,dhcp_entries d " .
	   "WHERE z.server=$serverid AND h.zone=z.id AND d.type=3 " .
	   " AND d.ref=h.id ORDER BY h.id,d.id",\@q);
  for $i (0..$#q) {
    $dhcphosth{$q[$i][0]}=[] unless ($dhcphosth{$q[$i][0]});
    $dhcphosthd{$q[$i][0]}=[] unless ($dhcphosthd{$q[$i][0]});
    push @{$dhcphosth{$q[$i][0]}}, $q[$i][1];
    push @{$dhcphosthd{$q[$i][0]}}, $q[$i][2];
  }


  # global entries

  print DHCPFILE "\n# global options\n\n";
  undef @q;
  db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
	   "WHERE d.type=1 AND d.ref=$serverid ORDER BY d.id",\@q);
  for $i (0..$#q) {
      $q[$i][0].=';' unless ($q[$i][0] =~ /[;\{\}]\s*$/);
      if($q[$i][1] ne '') {
        print DHCPFILE "$q[$i][0]\t # $q[$i][1]\n";
      }
      else {
        print DHCPFILE "$q[$i][0]\n";
      }
  }
  print DHCPFILE "\n\n";

  #Host declaration for dyn. classes 
  my $hddc = "";

  # dynamic hosts (DHCP classes)
  unless ($dhcp2_mode) {
  print_dhcp_classes:
    undef @dclasses;
    db_query("SELECT id,name,type FROM groups " .
	     "WHERE server=$serverid AND (type=3 OR type=103) " .
	     ($opt_dhcpclass ?
	      " AND name=".db_encode_str($opt_dhcpclass)." " : "") .
	     "ORDER BY name",\@dclasses);

    if (@dclasses > 0) {
      print DHCPFILE "\n# (dynamic) classes\n\n";
      for $i (0..$#dclasses) {
	 undef @qq;
     db_query("SELECT h.ether,h.domain " .
		   " FROM zones z JOIN hosts h ON z.id=h.zone " .
		   " LEFT JOIN group_entries g ON h.id=g.host " .
		   " WHERE z.server=$serverid AND h.ether NOTNULL " .
		   " AND (h.grp=$dclasses[$i][0] OR g.grp=$dclasses[$i][0]) " .
           " AND (h.expiration > EXTRACT(epoch FROM current_timestamp) OR h.expiration = 0 OR h.expiration ISNULL) " .
		   " ORDER BY h.ether",\@qq);

     next unless(@qq); 
    
    print DHCPFILE "\nclass \"$dclasses[$i][1]\" {\n";
	undef @q;
	db_query("SELECT dhcp, comment FROM dhcp_entries " .
		 "WHERE type=5 and ref=$dclasses[$i][0] ORDER BY id",\@q);
	for $j (0..$#q) {
	    $q[$j][0].=';' unless ($q[$j][0] =~ /[;\{\}]\s*$/);
	    print DHCPFILE "\t$q[$j][0]\t #$q[$j][1]\n";
	}
	print DHCPFILE "}\n\n";

	# subclasses
	if ($dclasses[$i][2]== 3) {
	  my $lastmac;
	  for $j (0..$#qq) {
	    unless ($qq[$j][0] =~ /^[0-9A-F]{12}$/) {
	      #error("invalid ether for subclass: $q[$j][0]");
	      next;
	    }
	    next if ($qq[$j][0] eq $lastmac);
	    print DHCPFILE "subclass \"$dclasses[$i][1]\" 1:" .
	                   dhcpether($qq[$j][0]) . ";  # $qq[$j][1]\n";
	    
        $hddc .= sprintf("host %s-%s { hardware ethernet %s; }\n", $qq[$j][1], $dclasses[$i][1], dhcpether($qq[$j][0]));
        $lastmac=$qq[$j][0];
	  }
	}

      }
      print DHCPFILE "\n";
    }

    goto dhcpd_conf_done if ($opt_dhcpclass);
  }


  # check for dynamic IP pools
  undef @dpools;
  db_query("SELECT id,name FROM groups WHERE server=$serverid AND type=2 ".
	   "ORDER BY name",\@dpools);
  if (@dpools > 0) {
    $dpools_count=@dpools;
    print STDERR "Found $dpools_count dynamic IP pools\n" if ($opt_verbose);
    undef @dips;
    db_query("SELECT g.id,a.ip,h.domain ".
	     "FROM hosts h, a_entries a, groups g ".
	     "WHERE a.host=h.id AND g.id=h.grp AND g.type=2 ".
	     " AND g.server=$serverid AND family(a.ip) = 4 ORDER BY a.ip,g.id",\@dips);
    $dpool_ip_count=@dips;
    print STDERR "Found $dpool_ip_count dynamic pool IPs\n" if ($opt_verbose);


    undef %dpools_dhcp;
    for $i (0..$#dpools) {
      undef @q;
      db_query("SELECT dhcp FROM dhcp_entries " .
	       "WHERE type=5 AND ref=$dpools[$i][0] ORDER BY id",\@q);
      $dpools_dhcp{$i}=[];
      for $j (0..$#q) { push @{$dpools_dhcp{$i}}, $q[$j][0]; }
    }
  }

  # net/subnet map

  $net_map_mode=2;  # 0=use VLANs, 1=use networks, 2= use VLANs, pools in nets

  print DHCPFILE "\n# network map\n\n";
  undef @subnets;
  undef @nets;
  db_query("SELECT net,name,id,vlan " .
	   "FROM nets WHERE server=$serverid AND no_dhcp=false " .
	   "AND subnet=false AND dummy=false AND family(net) = 4 ORDER BY net",\@nets);
  if (@nets < 1) {
    fatal("No nets, that use DHCP, defined for this server!");
  }
  for $i (0 .. $#nets) {
    $net=$nets[$i][0];
    $netname=$nets[$i][1];
    $netname =~ s/\s+//g;
    if ($net_map_mode == 1) {
      print DHCPFILE "shared-network \"$netname\" {\n";
      undef @net_dhcp;
      db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
	       "WHERE d.type=4 AND d.ref=$nets[$i][2] ORDER BY d.id",
	       \@net_dhcp);
      for $j (0..$#net_dhcp) { 
	  $net_dhcp[$i][0].=';' unless ($net_dhcp[$i][0] =~ /[;\{\}]\s*$/);
          if($net_dhcp[$i][1] ne '') {
            print DHCPFILE "\t$net_dhcp[$i][0]\t # $net_dhcp[$i][1]\n";
          }
          else {
            print DHCPFILE "\t$net_dhcp[$i][0]\n";
          }
      }
      
print DHCPFILE "\n";
    }

    undef @q;
    db_query("SELECT net,id,name,vlan FROM nets " .
	     "WHERE server=$serverid AND no_dhcp=false AND subnet=true " .
	     "AND dummy=false AND net << '$net' AND family(net) = 4 ORDER BY net;",\@q);
    if (@q < 1 ) {
      error("No subnets that use DHCP found") if ($net_map_mode==1);
      push @q, [$net,$nets[$i][2],$nets[$i][1],$nets[$i][3]];
    }

    if ($net_map_mode == 1) {
      print_subnets();
      print DHCPFILE "}\n\n";
    } else {
      push (@subnets,@q);
    }
  }

  if ($net_map_mode == 0 or $net_map_mode == 2 ) {
    # print network map using VLANs...
    undef %vlanhash;
    for $i (0..$#subnets) {
      $vlanhash{$subnets[$i][3]}++;
    }
    undef @vlan_list_lst;
    undef %vlan_list_hash;
    get_vlan_list($serverid,\%vlan_list_hash,\@vlan_list_lst);
    foreach $vlan (sort keys %vlanhash) {
      $netname=$vlan_list_hash{$vlan};
      $netname =~ s/\s//g;
      $netname = 'CHAOS' if ($vlan < 1 || $netname eq '');
      print DHCPFILE "shared-network \"$netname\" {\n";
      undef @vlan_dhcp;
      db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
	       "WHERE d.type=6 AND d.ref=$vlan ORDER BY d.id",\@vlan_dhcp);
      for $i (0..$#vlan_dhcp) {
	$vlan_dhcp[$i][0].=';' unless ($vlan_dhcp[$i][0] =~ /[;\{\}]\s*$/);
        if($vlan_dhcp[$i][1] ne '') {
	        print DHCPFILE "\t$vlan_dhcp[$i][0]\t # $vlan_dhcp[$i][1]\n";
        }
        else {
	        print DHCPFILE "\t$vlan_dhcp[$i][0]\n";
        } 
     }
      undef @q;
      for $i (0..$#subnets) {
	push ( @q, $subnets[$i])  if ($subnets[$i][3] eq $vlan);
      }
      print_subnets();
      print DHCPFILE "}\n\n";
    }
  }


  # groups

  print DHCPFILE "# groups\n\n";

  undef @groups;
  db_query("SELECT id,name FROM groups WHERE server=$serverid AND " .
	   #"(type=1 OR type=3) " .
	   "type=1 " .
	   "ORDER BY name;",\@groups);
  if (@groups < 1) {
    error("no groups found for this server");
  }
  else {
    undef @ghosts;
    db_query("SELECT h.grp,h.ether,a.ip,h.domain,z.name,h.id,h.expiration " .
	     "FROM hosts h, zones z, a_entries a " .
	     "WHERE h.zone=z.id AND a.host=h.id AND h.type=1 " .
	     "AND h.ether NOTNULL AND z.server=$serverid " .
	     "AND h.grp>-1 AND family(a.ip) = 4 " .
	     "ORDER BY a.ip;",\@ghosts);

    if (@ghosts > 0) {
      for $i (0.. $#groups) {
	$gid=$groups[$i][0];
	$group=$groups[$i][1];
	# $dhcp=db_decode_list_str($groups[$i][2]);
    undef @qqq;
	undef @gperhost; # per host macro store
	undef @qcount;
    
    db_query("SELECT COUNT(h.id) " .
             "FROM hosts h, a_entries a " .
             "WHERE a.host=h.id AND grp = $gid AND family(a.ip) = 4 AND ether IS NOT NULL",\@qcount);
    next unless $qcount[0][0];

	db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
		 "WHERE d.type=5 AND d.ref=$gid ORDER BY d.id",\@qqq);
	
      print DHCPFILE "group {  # $group\n";
    
      for $k (0..$#qqq) {
	  $qqq[$k][0] =~ s/\\"/"/g;
	  $qqq[$k][0].=';' unless ($qqq[$k][0] =~ /[;\{\}]\s*$/);

	  # if includes %{macro} store it for later use and skip to next 
	  if ($qqq[$k][0] =~ /\%\{\w+\}/) { 
	      push(@gperhost,$qqq[$k][0]);    
	      next;
	  }
      if($qqq[$k][1] ne '') {
	    print DHCPFILE "\t$qqq[$k][0]\t #$qqq[$k][1]\n";
      }
      else {
	    print DHCPFILE "\t$qqq[$k][0]\n";
      }
	}
	print DHCPFILE "\t\n";

	for $j (0 .. $#nets) {
	  $net=new Net::Netmask($nets[$j][0]);
	  print DHCPFILE "\t# net: " .$net->desc() . "\n";
	  for $k (0 .. $#ghosts) {
	    if (($ghosts[$k][6] > 0) && ($ghosts[$k][6] < $time_now_ticks)) {
	      #error("expired host in DHCP: $ghosts[$k][3]");
	      next;
	    }
	    $iplst=$etherhash{$ghosts[$k][1]};
	    @{$iplst} = sort(@{$iplst}) if (@{$iplst}>1);
	    $ip='';
	    for $l (0..$#{$iplst}) {
	      if ($net->match($$iplst[$l])) {
		$ip.=", " if ($ip ne '');
		$ip.=$$iplst[$l];
	      }
	    }
	    #print "IP '$ip'\n" if ($ip =~ /,/);
	    if ($ghosts[$k][0]==$gid && ($ip ne '')) {
	      $host=$ghosts[$k][3];
	      $host.=".$ghosts[$k][4]" unless ($host =~ /\.$/);
	      $host =~ s/\.$//;
	      $ether=dhcpether($ghosts[$k][1]);
	      #$dhcp=db_decode_list_str($ghosts[$k][5]);
	      $dhcpid=$ghosts[$k][5];
	      if ($ethers{$ether}>0) {
		error("duplicate interface $ip for $ether ($host)");
		next;
	      }
	      $ethers{$ether}++;
	      print DHCPFILE "\thost $host {\n";
	      print DHCPFILE "\t\tfixed-address $ip;\n";
	      print DHCPFILE "\t\thardware ethernet $ether;\n";

	      $dhcplst=$dhcphosth{$dhcpid};
	      ($adomain=$host) =~ s/^[^\.]+\.//;
	      $fqdn = $host;
	      $host =~ s/\..*//g;
	      $lookup = '/(filename|option\s+root-path)\s+/';
	      foreach $l (@gperhost) {
		  push @{$dhcplst},$l unless (grep {eval $lookup} @{$dhcplst});
	      }

	      if ($dhcp_auto_domainnames) {
		  push(@{$dhcplst},"option domain-name \"$adomain\"")
		      unless (grep {/option\s+domain-name\s+/ } @{$dhcplst}
			      and $adomain);
	      }
	      
	      %dhcp_var = ();
	      $dhcp_var{'domain'} = $adomain;
	      $dhcp_var{'ether'}  = $ether;
	      $dhcp_var{'fqdn'}   = $fqdn;
	      $dhcp_var{'host'}   = $host;

	      for $l (0..$#{$dhcplst}) {
		    $$dhcplst[$l].=';' unless ($$dhcplst[$l] =~ /[;\{\}]\s*$/);
		    if(@{$dhcphosthd{$dhcpid}}[$l] ne '') {
                print DHCPFILE "\t\t" . expand_dhcp_macro($$dhcplst[$l],\%dhcp_var)."\t#" . @{$dhcphosthd{$dhcpid}}[$l] ."\n";
	        }
            else {
                print DHCPFILE "\t\t" . expand_dhcp_macro($$dhcplst[$l],\%dhcp_var) . "\n";
            }
          }
	      print DHCPFILE "\t}\n";
	    }
	  }
	}
	print DHCPFILE "}\n\n";
      }
    } else {
      print "no hosts found for any groups!\n";
    }
  }

  # not grouped hosts...
  print DHCPFILE "\n# hosts not in any group\n\n";
  undef @ghosts;
  db_query("SELECT h.grp,h.ether,a.ip,h.domain,z.name,h.id,h.expiration " .
	   "FROM hosts h, zones z, a_entries a " .
	   "WHERE h.zone=z.id AND a.host=h.id AND ( h.type=1 OR h.type=9 ) " .
	   "AND h.ether NOTNULL AND z.server=$serverid " .
	   "AND h.grp<0 AND family(a.ip) = 4" .
	   "ORDER BY a.ip;",\@ghosts);
  if (@ghosts > 0 ) {
    for $j (0 .. $#nets) {
      $net=new Net::Netmask($nets[$j][0]);
      print DHCPFILE "# net: " .$net->desc() . "\n";
      for $k (0 .. $#ghosts) {
	if (($ghosts[$k][6] > 0) && ($ghosts[$k][6] < $time_now_ticks)) {
	  # error("expired host in DHCP: $ghosts[$k][3]");
	  next;
	}
	$iplst=$etherhash{$ghosts[$k][1]};
	@{$iplst} = sort(@{$iplst}) if (@{$iplst}>1);
	$ip='';
	for $l (0..$#{$iplst}) {
	  if ($net->match($$iplst[$l])) {
	    $ip.=", " if ($ip ne '');
	    $ip.=$$iplst[$l];
	  }
	}
	#print "IP '$ip'\n" if ($ip =~ /,/);
	if ($ip ne '') {
	  $host=$ghosts[$k][3];
	  $host.=".$ghosts[$k][4]" unless ($host =~ /\.$/);
	  $host =~ s/\.$//;
	  $ether=dhcpether($ghosts[$k][1]);
	  #$dhcp=db_decode_list_str($ghosts[$k][5]);
	  $dhcpid=$ghosts[$k][5];
	  if ($ethers{$ether}>0) {
	    #error("duplicate interface $ip for $ether ($host)");
	    next;
	  }
	  $ethers{$ether}++;
	  print DHCPFILE "host $host {\n";
	  print DHCPFILE "\tfixed-address $ip;\n";
	  print DHCPFILE "\thardware ethernet $ether;\n";
	  if ($dhcp_auto_domainnames) {
	    ($adomain=$host) =~ s/^[^\.]+\.//;
	    print DHCPFILE "\toption domain-name \"$adomain\";\n"
	      if ($adomain);
	  }

	  $dhcplst=$dhcphosth{$dhcpid};

	  for $l (0..$#{$dhcplst}) {
	      $$dhcplst[$l].=';' unless ($$dhcplst[$l] =~ /[;\{\}]\s*$/);
	      if(@{$dhcphosthd{$dhcpid}}[$l] ne '') {
            print DHCPFILE "\t$$dhcplst[$l]\t #". @{$dhcphosthd{$dhcpid}}[$l] . "\n";
          }
          else {
            print DHCPFILE "\t$$dhcplst[$l]\n";
          }
	  }
	  print DHCPFILE "}\n";
	}
      }
    }
  }

  undef @ghosts;
  undef %ethers;

  print DHCPFILE "\n#Host declaration for dynamic classes\n";
  print DHCPFILE $hddc;

  print DHCPFILE "\n# eof\n";
  close(DHCPFILE);

  # if --check specified, validate dhcpd.conf
  if ($opt_check && $SAURON_DHCP_CHK_PROG) {
    print "Validating $dhcpd_conf_filename...\n" if ($opt_verbose);
    fatal("cannot run SAURON_DHCP_CHK_PROG: $SAURON_DHCP_CHK_PROG")
      unless (-x $SAURON_DHCP_CHK_PROG);

    @args = split(/\s+/,$SAURON_DHCP_CHK_ARGS);
    push @args, "$dhcpd_conf_filename" . $tmp_extension;
    print "run: $SAURON_DHCP_CHK_PROG " .join(' ',@args)."\n" 
	if ($opt_verbose);
    $res = system($SAURON_DHCP_CHK_PROG,@args);
    fatal("$dhcpd_conf_filename validity check failed ($res)") if ($res);
  }


 dhcpd_conf_done:
  # remove $tmp_extension from generated file name
  foreach $tmpfile (keys %open_tmpfiles) {
    print "rename: $tmpfile --> $open_tmpfiles{$tmpfile}\n" if ($opt_verbose);
    fatal("failed to rename tmpfile: $tmpfile")
      unless(rename($tmpfile,$open_tmpfiles{$tmpfile})==1);
    delete $open_tmpfiles{$tmpfile};
  }

}


sub make_dhcp6() {
  print "DHCP6 configuration\n";
  $time_now = localtime;

  # dhcpd.conf
  $dhcpd_conf_filename = ($opt_dhcpclass ? "$opt_dhcpclass.class6" :
			  "dhcpd6.conf");
  print "Generating $dhcpd_conf_filename...\n";

  open(DHCPFILE,">$dhcpd_conf_filename$tmp_extension")
    || fatal("Cannot create $dhcpd_conf_filename$tmp_extension!");
  $open_tmpfiles{"$dhcpd_conf_filename" . $tmp_extension} =
    "$dhcpd_conf_filename";
  print DHCPFILE "# $dhcpd_conf_filename -- automagically generated ",
                 "by Sauron v$VER\n";
  print DHCPFILE "#               created by $user at $time_now\n#\n",
                 "# server: $server{name} -- $server{comment}\n#\n\n";

  $serverid=$server{masterserver} if ($server{masterserver} > 0);
  goto print_dhcp_classes if ($opt_dhcpclass);


  # failover stuff
  if ($failover_mode) {
    $foserver=($failover_mode == 1 ? \%server : \%masterserver);
    $fo_host=$server{hostname};
    $fo_port=$fo_pport=$foserver->{df_port6};
    $fo_mrd=$foserver->{df_max_delay6};
    $fo_muu=$foserver->{df_max_uupdates6};
    $fo_mclt=$foserver->{df_mclt6};
    $fo_split=$foserver->{df_split6};
    $fo_lbm=$foserver->{df_loadbalmax6};
    if ($failover_mode == 1) {
      undef @q;
      db_query("SELECT hostname FROM servers " .
	       "WHERE masterserver=$server{id}",\@q);
      error("cannot find secondary failover peer!") unless ($q[0][0]);
      $fo_peer=$q[0][0];
    } else {
      $fo_peer=$foserver->{hostname};
    }
    $fo_host =~ s/\.\s*$//;
    $fo_peer =~ s/\.\s*$//;


    print DHCPFILE "failover peer \"$failover_peer_name\" {\n",
                   "\t".($failover_mode == 1 ? 'primary':'secondary').";\n",
		   "\taddress $fo_host;\n",
		   "\tport $fo_port;\n",
		   "\tpeer address $fo_peer;\n",
		   "\tpeer port $fo_pport;\n",
		   "\tmax-response-delay $fo_mrd;\n",
		   "\tmax-unacked-updates $fo_muu;\n",
		   "\tmclt $fo_mclt;\n",
		   ($failover_mode == 1 ? "\tsplit $fo_split;\n" : ''),
		   "\tload balance max seconds $fo_lbm;\n}\n\n";
  }

  # duid hash
  undef @q;
  db_query("SELECT h.duid,a.ip FROM hosts h,zones z,a_entries a " .
	   "WHERE z.server=$serverid AND h.zone=z.id AND a.host=h.id " .
	   " AND h.duid NOTNULL AND family(a.ip) = 6;",\@q);
  error(db_errormsg()) if (db_errormsg());
  for $i (0..$#q) {
    unless ($q[$i][0] =~ /^[0-9A-Fa-f]{24,40}\s*$/) {
      error("Invalid DUID ($q[$i][0]) skipped");
      next;
    }
    $duidhash{$q[$i][0]}=[] unless ($duidhash{$q[$i][0]});
    push @{$duidhash{$q[$i][0]}}, $q[$i][1];
  }

  # host dhcp hash
  undef @q;
  db_query("SELECT h.id,d.dhcp, d.comment FROM hosts h,zones z,dhcp_entries d " .
	   "WHERE z.server=$serverid AND h.zone=z.id AND d.type=13 " .
	   " AND d.ref=h.id ORDER BY h.id,d.id",\@q);
  for $i (0..$#q) {
    $dhcphosth{$q[$i][0]}=[] unless ($dhcphosth{$q[$i][0]});
    $dhcphosthd{$q[$i][0]}=[] unless ($dhcphosthd{$q[$i][0]});
    push @{$dhcphosth{$q[$i][0]}}, $q[$i][1];
    push @{$dhcphosthd{$q[$i][0]}}, $q[$i][2];
  }


  # global entries

  print DHCPFILE "\n# global options\n\n";
  undef @q;
  db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
	   "WHERE d.type=11 AND d.ref=$serverid ORDER BY d.id",\@q);
  for $i (0..$#q) {
      $q[$i][0].=';' unless ($q[$i][0] =~ /[;\{\}]\s*$/);
      if($q[$i][1] ne '') {
        print DHCPFILE "$q[$i][0]\t# $q[$i][1]\n";
      }
      else {
        print DHCPFILE "$q[$i][0]\n";
      }
  }
  print DHCPFILE "\n\n";

  #Host declaration for dyn. classes 
  my $hddc = "";

  # dynamic hosts (DHCP classes)
  unless ($dhcp2_mode) {
  print_dhcp_classes:
    undef @dclasses;
    db_query("SELECT id,name,type FROM groups " .
	     "WHERE server=$serverid AND (type=3 OR type=103) " .
	     ($opt_dhcpclass ?
	      " AND name=".db_encode_str($opt_dhcpclass)." " : "") .
	     "ORDER BY name",\@dclasses);
    if (@dclasses > 0) {
      print DHCPFILE "\n# (dynamic) classes\n\n";
      for $i (0..$#dclasses) {
      undef @qq;
      db_query("SELECT h.duid,h.domain " .
           " FROM zones z JOIN hosts h ON z.id=h.zone " .
           " LEFT JOIN group_entries g ON h.id=g.host " .
           " WHERE z.server=$serverid AND h.duid NOTNULL " .
           " AND (h.grp=$dclasses[$i][0] OR g.grp=$dclasses[$i][0]) " .
           " AND (h.expiration > EXTRACT(epoch FROM current_timestamp) OR h.expiration = 0 OR h.expiration ISNULL) " .
           " ORDER BY h.duid",\@qq);

    next unless(@qq);

	print DHCPFILE "\nclass \"$dclasses[$i][1]\" {\n";
    undef @q;
	db_query("SELECT dhcp, comment FROM dhcp_entries " .
		 "WHERE type=15 and ref=$dclasses[$i][0] ORDER BY id",\@q);
	for $j (0..$#q) {
	    $q[$j][0].=';' unless ($q[$j][0] =~ /[;\{\}]\s*$/);
	     if($q[$j][1] ne '') {
            print DHCPFILE "\t$q[$j][0]\t #$q[$j][1]\n";
         }
         else {
            print DHCPFILE "\t$q[$j][0]\n";
         }
	}
	print DHCPFILE "}\n\n";

	# subclasses
	if ($dclasses[$i][2]== 3) {
      my $lastduid;
      for $j (0..$#qq) {
	    unless ($qq[$j][0] =~ /^[a-f0-9A-F]{24,40}$/) {
	      error("invalid duid for subclass: $qq[$j][0]");
	      next;
	    }
	    next if ($qq[$j][0] eq $lastduid);
	    print DHCPFILE "subclass \"$dclasses[$i][1]\" ".  dhcpduid($qq[$j][0]) .  ";  # $qq[$j][1]\n";
	  
        $hddc .= sprintf("host %s-%s { host-identifier option dhcp6.client-id %s; }\n", $qq[$j][1], $dclasses[$i][1], dhcpduid($qq[$j][0]));
        $lastduid=$qq[$j][0];
	  }
	}

      }
      print DHCPFILE "\n";
    }

    goto dhcpd_conf_done if ($opt_dhcpclass);
  }


  # check for dynamic IP pools
  undef @dpools;
  db_query("SELECT id,name FROM groups WHERE server=$serverid AND type=2 ".
	   "ORDER BY name",\@dpools);

  if (@dpools > 0) {
    $dpools_count=@dpools;
    print STDERR "Found $dpools_count dynamic IPv6 pools\n" if ($opt_verbose);
    undef @dips;
    db_query("SELECT g.id,a.ip,h.domain ".
	     "FROM hosts h, a_entries a, groups g ".
	     "WHERE a.host=h.id AND g.id=h.grp AND g.type=2 ".
	     " AND g.server=$serverid AND family(a.ip) = 6 ORDER BY a.ip,g.id",\@dips);
    $dpool_ip_count=@dips;
    print STDERR "Found $dpool_ip_count dynamic pool IPs\n" if ($opt_verbose);


    undef %dpools_dhcp;
    for $i (0..$#dpools) {
      undef @q;
      db_query("SELECT dhcp FROM dhcp_entries " .
	       "WHERE type=15 AND ref=$dpools[$i][0] ORDER BY id",\@q);
      $dpools_dhcp{$i}=[];
      for $j (0..$#q) { push @{$dpools_dhcp{$i}}, $q[$j][0]; }
    }
  }

  # net/subnet map
  $net_map_mode= 2;  # 0=use VLANs, 1=use networks, 2= use VLANs, pools in nets

  print DHCPFILE "\n# network map\n\n";
  undef @subnets;
  undef @nets;
  db_query("SELECT net,name,id,vlan " .
	   "FROM nets WHERE server=$serverid AND no_dhcp=false " .
	   "AND subnet=false AND dummy=false AND family(net) = 6 ORDER BY net",\@nets);
  if (@nets < 1) {
    fatal("No nets, that use DHCP6, defined for this server!");
  }
  for $i (0 .. $#nets) {
    $net=$nets[$i][0];
    $netname=$nets[$i][1];
    $netname =~ s/\s+//g;
    if ($net_map_mode == 1) {
      print DHCPFILE "shared-network \"$netname\" {\n";
      undef @net_dhcp;
      db_query("SELECT d.dhcp FROM dhcp_entries d " .
	       "WHERE d.type=14 AND d.ref=$nets[$i][2] ORDER BY d.id",
	       \@net_dhcp);
      for $j (0..$#net_dhcp) { 
	  $net_dhcp[$i][0].=';' unless ($net_dhcp[$i][0] =~ /[;\{\}]\s*$/);
	  print DHCPFILE "\t$net_dhcp[$i][0]\n";
      }
      print DHCPFILE "\n";
    }

    undef @q;
    db_query("SELECT net,id,name,vlan FROM nets " .
	     "WHERE server=$serverid AND no_dhcp=false AND subnet=true " .
	     "AND dummy=false AND net << '$net' AND family(net) = 6 ORDER BY net;",\@q);

    if (@q < 1 ) {
      error("No subnets that use DHCP found") if ($net_map_mode==1);
      push @q, [$net,$nets[$i][2],$nets[$i][1],$nets[$i][3]];
    }

    if ($net_map_mode == 1) {
      print_subnets6();
      print DHCPFILE "}\n\n"; # close bracket for shared-network
    }
    else {
      push (@subnets,@q);
    }
  }

  if ($net_map_mode == 0 or $net_map_mode == 2) {
    # print network map using VLANs...
    undef %vlanhash;
    for $i (0..$#subnets) {
      $vlanhash{$subnets[$i][3]}++;
    }
    undef @vlan_list_lst;
    undef %vlan_list_hash;
    get_vlan_list($serverid,\%vlan_list_hash,\@vlan_list_lst);
    foreach $vlan (sort keys %vlanhash) {
      $netname=$vlan_list_hash{$vlan};
      $netname =~ s/\s//g;
      $netname = 'CHAOS' if ($vlan < 1 || $netname eq '');
      print DHCPFILE "shared-network \"$netname\" {\n";
      undef @vlan_dhcp;
      db_query("SELECT d.dhcp FROM dhcp_entries d " .
	       "WHERE d.type=16 AND d.ref=$vlan ORDER BY d.id",\@vlan_dhcp);
      for $i (0..$#vlan_dhcp) {
	$vlan_dhcp[$i][0].=';' unless ($vlan_dhcp[$i][0] =~ /[;\{\}]\s*$/);
	print DHCPFILE "\t$vlan_dhcp[$i][0]\n";
      }
      undef @q;
      for $i (0..$#subnets) {
	push ( @q, $subnets[$i])  if ($subnets[$i][3] eq $vlan);
      }
      print_subnets6();
      print DHCPFILE "}\n\n";
    }
  }


  # groups

  print DHCPFILE "# groups\n\n";

  undef @groups;
  db_query("SELECT id,name FROM groups WHERE server=$serverid AND " .
	   "type=1 " .
	   "ORDER BY name;",\@groups);
  if (@groups < 1) {
    error("no groups found for this server");
  }
  else {
    undef @ghosts;
    db_query("SELECT h.grp,h.duid,a.ip,h.domain,z.name,h.id,h.expiration " .
	     "FROM hosts h, zones z, a_entries a " .
	     "WHERE h.zone=z.id AND a.host=h.id AND h.type=1 " .
	     "AND h.duid NOTNULL AND z.server=$serverid " .
	     "AND h.grp>-1 AND family(a.ip) = 6" .
	     "ORDER BY a.ip;",\@ghosts);
    if (@ghosts > 0) {
      for $i (0.. $#groups) {
	$gid=$groups[$i][0];
	$group=$groups[$i][1];
	# $dhcp=db_decode_list_str($groups[$i][2]);
	undef @qqq;
	undef @gperhost; # per host macro store
    undef @qcount;
    
    db_query("SELECT COUNT(h.id) " .
             "FROM hosts h, a_entries a " .
             "WHERE a.host=h.id AND grp = $gid AND family(a.ip) = 6 AND duid IS NOT NULL",\@qcount);
    next unless $qcount[0][0];

	db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
		 "WHERE d.type=15 AND d.ref=$gid ORDER BY d.id",\@qqq);

	print DHCPFILE "group {  # $group\n";

	for $k (0..$#qqq) {
	  $qqq[$k][0] =~ s/\\"/"/g;
	  $qqq[$k][0].=';' unless ($qqq[$k][0] =~ /[;\{\}]\s*$/);

	  # if includes %{macro} store it for later use and skip to next 
	  if ($qqq[$k][0] =~ /\%\{\w+\}/) { 
	      push(@gperhost,$qqq[$k][0]);    
	      next;
	  }
      if($qqq[$k][1] ne '') {
	    print DHCPFILE "\t$qqq[$k][0]\t #$qqq[$k][1]\n";
	  }
      else {
        print DHCPFILE "\t$qqq[$k][0]\n";
      }
    }
	print DHCPFILE "\t\n";

	for $j (0 .. $#nets) {
	  $net = new Net::IP($nets[$j][0]);
      print DHCPFILE "\t# net: " . ip_compress_address($net->ip(), 6) . "\n";
	  for $k (0 .. $#ghosts) {
	    if (($ghosts[$k][6] > 0) && ($ghosts[$k][6] < $time_now_ticks)) {
	      # error("expired host in DHCP: $ghosts[$k][3]");
	      next;
	    }
	    $iplst=$duidhash{$ghosts[$k][1]};
	    @{$iplst} = sort(@{$iplst}) if (@{$iplst}>1);
	    $ip='';
	    for $l (0..$#{$iplst}) {
            if ($net->overlaps(new Net::IP($$iplst[$l])) == $IP_B_IN_A_OVERLAP) {
		$ip.=", " if ($ip ne '');
		$ip.=$$iplst[$l];
	      }
	    }
	    #print "IP '$ip'\n" if ($ip =~ /,/);
	    if ($ghosts[$k][0]==$gid && ($ip ne '')) {
	      $host=$ghosts[$k][3];
	      $host.=".$ghosts[$k][4]" unless ($host =~ /\.$/);
	      $host =~ s/\.$//;
	      $duid= $ghosts[$k][1];
	      #$dhcp=db_decode_list_str($ghosts[$k][5]);
	      $dhcpid=$ghosts[$k][5];
	      if ($duids{$duid}>0) {
		    error("duplicate interface $ip for $duid ($host)");
		    next;
	      }
	      $duids{$duid}++;
	      print DHCPFILE "\thost $host {\n";
	      print DHCPFILE "\t\tfixed-address6 $ip;\n";
	      print DHCPFILE "\t\thost-identifier option dhcp6.client-id " . dhcpduid($duid) . ";\n";

	      $dhcplst=$dhcphosth{$dhcpid};
	      ($adomain=$host) =~ s/^[^\.]+\.//;
	      $fqdn = $host;
	      $host =~ s/\..*//g;
	      $lookup = '/(filename|option\s+root-path)\s+/';
	      foreach $l (@gperhost) {
		  push @{$dhcplst},$l unless (grep {eval $lookup} @{$dhcplst});
	      }

	      if ($dhcp_auto_domainnames) {
		  push(@{$dhcplst},"option dhcp6.name-servers \"$adomain\"")
		      unless (grep {/option\s+dhcp6\.name-servers\s+/ } @{$dhcplst}
			      and $adomain);
	      }
	      
	      %dhcp_var = ();
	      $dhcp_var{'domain'} = $adomain;
	      $dhcp_var{'duid'}  = $duid;
	      $dhcp_var{'fqdn'}   = $fqdn;
	      $dhcp_var{'host'}   = $host;

	      for $l (0..$#{$dhcplst}) {
		  $$dhcplst[$l].=';' unless ($$dhcplst[$l] =~ /[;\{\}]\s*$/);
		  #print DHCPFILE "\t\t" . expand_dhcp_macro($$dhcplst[$l],\%dhcp_var)."\n";
            if(@{$dhcphosthd{$dhcpid}}[$l] ne '') {
                print DHCPFILE "\t\t" . expand_dhcp_macro($$dhcplst[$l],\%dhcp_var)."\t#" . @{$dhcphosthd{$dhcpid}}[$l] ."\n";
            }
            else {
                print DHCPFILE "\t\t" . expand_dhcp_macro($$dhcplst[$l],\%dhcp_var) . "\n";
            }
          }
	      print DHCPFILE "\t}\n";
	    }
	  }
	}
	print DHCPFILE "}\n\n";
      }
    } else {
      print "no hosts found for any groups!\n";
    }
  }

  # not grouped hosts...
  print DHCPFILE "\n# hosts not in any group\n\n";
  undef @ghosts;
  db_query("SELECT h.grp,h.duid,a.ip,h.domain,z.name,h.id,h.expiration " .
	   "FROM hosts h, zones z, a_entries a " .
	   "WHERE h.zone=z.id AND a.host=h.id AND ( h.type=1 OR h.type=9 ) " .
	   "AND h.duid NOTNULL AND z.server=$serverid " .
	   "AND h.grp<0 AND family(a.ip) = 6" .
	   "ORDER BY a.ip;",\@ghosts);
  if (@ghosts > 0 ) {
    for $j (0 .. $#nets) {
      $net=new Net::IP($nets[$j][0]);
      print DHCPFILE "# net: " .$net->short() . "\n";
      for $k (0 .. $#ghosts) {
	if (($ghosts[$k][6] > 0) && ($ghosts[$k][6] < $time_now_ticks)) {
	  # error("expired host in DHCP: $ghosts[$k][3]");
	  next;
	}
	$iplst=$duidhash{$ghosts[$k][1]};
	@{$iplst} = sort(@{$iplst}) if (@{$iplst}>1);
	$ip='';
	for $l (0..$#{$iplst}) {
	  if ($net->overlaps(new Net::IP($$iplst[$l])) == $IP_B_IN_A_OVERLAP) {
	    $ip.=", " if ($ip ne '');
	    $ip.=$$iplst[$l];
	  }
	}
	#print "IP '$ip'\n" if ($ip =~ /,/);
	if ($ip ne '') {
	  $host=$ghosts[$k][3];
	  $host.=".$ghosts[$k][4]" unless ($host =~ /\.$/);
	  $host =~ s/\.$//;
	  $duid=$ghosts[$k][1];
	  #$dhcp=db_decode_list_str($ghosts[$k][5]);
	  $dhcpid=$ghosts[$k][5];
	  if ($duids{$duid}>0) {
	    #error("duplicate interface $ip for $duid ($host)");
	    next;
	  }
	  $duids{$duid}++;
	  print DHCPFILE "host $host {\n";
	  print DHCPFILE "\tfixed-address6 $ip;\n";
	  print DHCPFILE "\thost-identifier option dhcp6.client-id " . dhcpduid($duid) . ";\n";
	  if ($dhcp_auto_domainnames) {
	    ($adomain=$host) =~ s/^[^\.]+\.//;
	    print DHCPFILE "\toption dhcp6.name-servers \"$adomain\";\n"
	      if ($adomain);
	  }

	  $dhcplst=$dhcphosth{$dhcpid};
	  
      for $l (0..$#{$dhcplst}) {
	      $$dhcplst[$l].=';' unless ($$dhcplst[$l] =~ /[;\{\}]\s*$/);
	      if(@{$dhcphosthd{$dhcpid}}[$l] ne '') {
            print DHCPFILE "\t$$dhcplst[$l]\t #" . @{$dhcphosthd{$dhcpid}}[$l] . "\n";
	      }
          else {
            print DHCPFILE "\t$$dhcplst[$l]\n";
          }
      }
	  print DHCPFILE "}\n";
	}
      }
    }
  }

  undef @ghosts;
  undef %duids;

  print DHCPFILE "\n#Host declaration for dynamic classes\n";
  print DHCPFILE $hddc;

  print DHCPFILE "\n# eof\n";
  close(DHCPFILE);

  # if --check specified, validate dhcpd.conf
  if ($opt_check && $SAURON_DHCP6_CHK_PROG) {
    print "Validating $dhcpd_conf_filename...\n" if ($opt_verbose);
    fatal("cannot run SAURON_DHCP6_CHK_PROG: $SAURON_DHCP6_CHK_PROG")
      unless (-x $SAURON_DHCP6_CHK_PROG);

    @args = split(/\s+/,$SAURON_DHCP6_CHK_ARGS);
    push @args, "$dhcpd_conf_filename" . $tmp_extension;
    print "run: $SAURON_DHCP6_CHK_PROG " .join(' ',@args)."\n" 
	if ($opt_verbose);
    $res = system($SAURON_DHCP6_CHK_PROG,@args);
    fatal("$dhcpd_conf_filename validity check failed ($res)") if ($res);
  }


 dhcpd_conf_done:
  # remove $tmp_extension from generated file name
  foreach $tmpfile (keys %open_tmpfiles) {
    print "rename: $tmpfile --> $open_tmpfiles{$tmpfile}\n" if ($opt_verbose);
    fatal("failed to rename tmpfile: $tmpfile")
      unless(rename($tmpfile,$open_tmpfiles{$tmpfile})==1);
    delete $open_tmpfiles{$tmpfile};
  }

}



sub print_subnets() {
  my(@qq,@qqq,$j,$k,$l,$router,$ssnet,$snet,$nmask,$dhcpid,$pri);
  my(@ranges,$lst,$s,$b,$e,$last_ip,$first_pool);

  for $k (0..$#dpools) { $ranges[$k]=[]; }

  db_query("SELECT ip,router FROM hosts,zones,a_entries " .
	       "WHERE hosts.zone=zones.id AND a_entries.host=hosts.id " .
	       "AND zones.server=$serverid AND router>0 AND family(ip) = 4 " .
	       ($net_map_mode == 1 ? "AND ip << '$net' " : "") .
	       "ORDER BY ip,router;",\@qq);
  error("No router interfaces found for net $net!") if (@qq < 1);

  for $j (0 .. $#q) {
    $ssnet = new Net::Netmask($q[$j][0]);
    $snet = $ssnet->base();
    $nmask = $ssnet->mask();
    $dhcpid = $q[$j][1];
    print DHCPFILE "\t# $q[$j][2]\n";
    print DHCPFILE "\tsubnet $snet netmask $nmask {\n";
    $pri=999999; # should be enough 'cos priority range for a router 0-999
    $router='';

    for $k (0 .. $#qq) {
      if ($ssnet->match($qq[$k][0]) && $pri > $qq[$k][1]) {
	$pri=$qq[$k][1];
	$router=$qq[$k][0];
      }
    }

    unless ($router eq '') {
      $router =~ s/\/\d{1,2}\s*$//g;
      print DHCPFILE "\t\toption routers $router;\n";
    } else {
      error("no router interface found for subnet: $snet $nmask");
    }

    undef @qqq;
    db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
	     "WHERE d.type=4 AND d.ref=$dhcpid ORDER BY d.id",\@qqq);
    for $k (0..$#qqq) {
	$qqq[$k][0].=';' unless ($qqq[$k][0] =~ /[;\{\}]\s*$/);
	    if($qqq[$k][1] ne '') {
            print DHCPFILE "\t\t$qqq[$k][0]\t # $qqq[$k][1]\n";
        } 
        else {
            print DHCPFILE "\t\t$qqq[$k][0]\n";
        }
    }

    print DHCPFILE "\t}\n" unless $net_map_mode == 2;
    
 
    # look for dynamic IP pools within this subnet
    if ($dpools_count > 0) {
      for $k (0..$#dpools) {
        $lst=$ranges[$k];
        $s=0;
        for $l (0..$#dips) {
            if (($dips[$l][0]==$dpools[$k][0]) && $ssnet->match($dips[$l][1])) {
            #print "foo: $snet $dips[$l][1]\n";
                if ($s==0) { $s=1; $b=$dips[$l][1]; }
                elsif ($s==1 && (ip2int($dips[$l][1]) > ip2int($last_ip)+1) ) {
                $e=$last_ip; 
                $line = "range " . ($b eq $e ? "$b" : "$b $e") .";";
                if ($net_map_mode == 2) {
                    print DHCPFILE "\t\tpool {\n\t\t\t$line\n";
                    for $x (0..$#{$dpools_dhcp{$k}}) {
                        print DHCPFILE "\t\t\t${$dpools_dhcp{$k}}[$x];\n";
                    }
                    print DHCPFILE "\t\t}\n";
                } 
                else {
                    push @{$lst}, $line;
                }

                print STDERR "dynamic range $b - $e ($dpools[$k][1])\n" if ($opt_verbose);
                $b=$dips[$l][1];
                }

                $last_ip=$dips[$l][1];
            } 
            else {
               if ($s==1) { 
                  $e=$last_ip; 
                  $line = "range " . ($b eq $e ? "$b" : "$b $e") .";";
                  if ($net_map_mode == 2) {
                    print DHCPFILE "\t\tpool {\n\t\t\t$line\n";
                    for $x (0..$#{$dpools_dhcp{$k}}) {
                        print DHCPFILE "\t\t\t${$dpools_dhcp{$k}}[$x];\n";
                    }
                    print DHCPFILE "\t\t}\n";
                  } 
                  else {
                      push @{$lst}, $line;
                  }
                
                  print STDERR "dynamic range $b - $e ($dpools[$k][1])\n" if ($opt_verbose);
                }
                $s=0;
            }
        }
        
        if ($s==1) { 
            $e=$last_ip; 
            $line = "range " . ($b eq $e ? "$b" : "$b $e") .";";
            if ($net_map_mode == 2) {
                print DHCPFILE "\t\tpool {\n\t\t\t$line\n";
                    for $x (0..$#{$dpools_dhcp{$k}}) {
                        print DHCPFILE "\t\t\t${$dpools_dhcp{$k}}[$x];\n";
                    }   
                
                print DHCPFILE "\t\t}\n";
            } 
            else 
            {
                push @{$lst}, $line;
            }
            print STDERR "dynamic range $b - $e ($dpools[$k][1])\n" if ($opt_verbose);
        }
        
      }
    }
    print DHCPFILE "\t}\n" if $net_map_mode == 2;
  }

  unless ($dhcp2_mode && $net_map_mode == 0) {
    $first_pool=1;
    for $j (0..$#dpools) {
      $lst=$ranges[$j];
      next unless (@{$lst} > 0);
      print DHCPFILE "\n\t# dynamic IP pools\n\n" if ($first_pool);
      $first_pool=0;
      print DHCPFILE "\tpool {  # $dpools[$j][1]\n";
      if ($failover_mode) {
	print DHCPFILE "\t\tfailover peer \"$failover_peer_name\";\n",
	               "\t\tdeny dynamic bootp clients;\n";
      }
      for $k (0..$#{$dpools_dhcp{$j}}) {
	print DHCPFILE "\t\t${$dpools_dhcp{$j}}[$k];\n";
      }
      for $k (0..$#{$lst}) {
	print DHCPFILE "\t\t$$lst[$k]\n";
      }
      print DHCPFILE "\t}\n\n";
    }
  }
}

sub print_subnets6() {
  my(@qq,@qqq,$j,$k,$l,$router,$ssnet,$snet,$nmask,$dhcpid,$pri);
  my(@ranges,$lst,$s,$b,$e,$last_ip,$first_pool);

  for $k (0..$#dpools) { $ranges[$k]=[]; }

  db_query("SELECT ip,router FROM hosts,zones,a_entries " .
	       "WHERE hosts.zone=zones.id AND a_entries.host=hosts.id " .
	       "AND zones.server=$serverid AND router>0 AND family(ip) = 6 " .
	       ($net_map_mode == 1 ? "AND ip << '$net' " : "") .
	       "ORDER BY ip,router;",\@qq);
  #error("No router interfaces found for net $net!") if (@qq < 1);

  for $j (0 .. $#q) {
    $ssnet = new Net::IP($q[$j][0]);
    $snet = $ssnet->ip();
    $dhcpid = $q[$j][1];
    print DHCPFILE "\t# $q[$j][2]\n";
    print DHCPFILE "\tsubnet6 " . ip_compress_address($snet,6) . "/" . $ssnet->prefixlen() . " {\n";

    $pri=999999; # should be enough 'cos priority range for a router 0-999
    $router='';
    for $k (0 .. $#qq) {
      if (($ssnet->overlaps(new Net::IP($qq[$k][0])) == $IP_B_IN_A_OVERLAP) && $pri > $qq[$k][1]) {
	$pri=$qq[$k][1];
	$router=$qq[$k][0];
      }
    }

    undef @qqq;
    db_query("SELECT d.dhcp, d.comment FROM dhcp_entries d " .
	     "WHERE d.type=4 AND d.ref=$dhcpid ORDER BY d.id",\@qqq);

    for $k (0..$#qqq) {
	$qqq[$k][0].=';' unless ($qqq[$k][0] =~ /[;\{\}]\s*$/);
        if($qqq[$k][1] ne '') {
            print DHCPFILE "\t\t$qqq[$k][0]\t # $qqq[$k][1]\n";
        }
        else {
            print DHCPFILE "\t\t$qqq[$k][0]\n";
        }
 
    }

    print DHCPFILE "\t}\n" unless $net_map_mode == 2;

    # look for dynamic IP pools within this subnet
    if ($dpools_count > 0) {
      for $k (0..$#dpools) {
        $lst=$ranges[$k];
        $s=0;
        for $l (0..$#dips) {
          if (($dips[$l][0]==$dpools[$k][0]) && $ssnet->overlaps(new Net::IP($dips[$l][1])) == $IP_B_IN_A_OVERLAP) {
            if ($s==0) { $s=1; $b=$dips[$l][1]; }
            elsif ($s==1 && ((new Net::IP($dips[$l][1]))->intip() > (new Net::IP($last_ip))->intip() + 1) ) {
                $e=$last_ip;
                $line = "range6 " . ($b eq $e ? "$b" : "$b $e") .";";
                if ($net_map_mode == 2) {
                    print DHCPFILE "\t\tpool6 {\n\t\t\t$line\n";
                    for $x (0..$#{$dpools_dhcp{$k}}) {
                        print DHCPFILE "\t\t\t${$dpools_dhcp{$k}}[$x];\n";
                    }
                    print DHCPFILE "\t\t}\n";
                }
                else {
                    push @{$lst}, $line;
                }

                print STDERR "dynamic range $b - $e ($dpools[$k][1])\n" if ($opt_verbose);
                $b=$dips[$l][1];
                }

                $last_ip=$dips[$l][1];
            }
            else {
               if ($s==1) {
                  $e=$last_ip;
                  $line = "range6 " . ($b eq $e ? "$b" : "$b $e") .";";
                  if ($net_map_mode == 2) {
                    print DHCPFILE "\t\tpool6 {\n\t\t\t$line\n";
                    for $x (0..$#{$dpools_dhcp{$k}}) {
                        print DHCPFILE "\t\t\t${$dpools_dhcp{$k}}[$x];\n";
                    }
                    print DHCPFILE "\t\t}\n";
                  }
                  else {
                      push @{$lst}, $line;
                  }

                  print STDERR "dynamic range $b - $e ($dpools[$k][1])\n" if ($opt_verbose);
                }
                $s=0;
            }
        }

        if ($s==1) {
            $e=$last_ip;
            $line = "range6 " . ($b eq $e ? "$b" : "$b $e") .";";
            if ($net_map_mode == 2) {
                    print DHCPFILE "\t\tpool6 {\n\t\t\t$line\n";
                    for $x (0..$#{$dpools_dhcp{$k}}) {
                        print DHCPFILE "\t\t\t${$dpools_dhcp{$k}}[$x];\n";
                    }
                    print DHCPFILE "\t\t}\n";
            }
            else
            {
                push @{$lst}, $line;
            }
            print STDERR "dynamic range $b - $e ($dpools[$k][1])\n" if ($opt_verbose);
        }
      }
    }
    print DHCPFILE "\t}\n" if $net_map_mode == 2;
  }
}

sub print_keys_acls($) {
  my($serverid) = @_;
  my(@keys,@reflist,%refs,%keyhash,$i);

  return unless ($serverid > 0);

  # get keys
  db_query("SELECT id,name,algorithm,mode,keysize,secretkey,comment " .
	   "FROM keys WHERE type=1 AND ref=$serverid " .
	   "ORDER BY name",\@keys);

  if (@keys > 0) {
      # count references to keys
      db_query("SELECT tkey FROM cidr_entries WHERE tkey > 0",\@reflist);
      for $i (0..$#reflist) { $refs{$reflist[$i][0]}++; }
  
      # mark keys that are not referenced
      my $keycount=0;
      for $i (0..$#keys) { 
	  unless ($refs{$keys[$i][0]}) { $keys[$i][0]=0; }
	  else { $keycount++; }
      }

      if ($keycount > 0) {
      
	  fatal("Encrypted keys used, but SAURON_KEY not defined!")
	      unless ($SAURON_KEY);

	  # print referenced keys
	  print BINDFILE "// Keys\n\n";
	  for $i (0..$#keys) { 
	      my $id = $keys[$i][0];
	      next unless ($id > 0);

	      my $name = $keys[$i][1];
	      my $algorithm = $algorithm_enum{$keys[$i][2]};
	      my $key = $keys[$i][5];
	      my $comment = $keys[$i][6];
    
	      unless ($name) {
		  error("Skipping key with empty name (id=$id)");
		  next;
	      }
	      unless ($algorithm) {
		  error("Skipping key with unsupported algorithm " .
			"($keys[$i][2]): $name");
		  next;
	      }
	      unless ($key) {
		  error("Skipping key with empty secret key: $name");
		  next;
	      }
	      my $h = Crypt::RC5->new($SAURON_KEY,16);
	      my $dkey = encode_base64($h->decrypt(decode_base64($key)));
	      chomp($dkey);

	      $comment =~ s/(^\s+|\s+$)//g;

	      print BINDFILE "key \"$name\" {\n",
		   ($comment ? "\t# $comment\n" : ''),
                   "\talgorithm $algorithm;\n",
		   "\tsecret \"$dkey\";\n",
		   
		   "};\n\n";
	      $dkey=$key;
	      $keyhash{$id}=1;
	  }
      }
  }

  # get ACLs...
  
  my(@acls,%aclhash);
  db_query("SELECT id,name,comment FROM acls " .
	   "WHERE server=$serverid ORDER BY id",\@acls);

  return unless (@acls > 0);

  print BINDFILE "// ACLs\n\n";

  # print ACLs...
  for $i (0..$#acls) {
    my($id,$name,$comment) = @{$acls[$i]};
    
    print BINDFILE "acl \"$name\" {\n";
    print BINDFILE "\t  // $comment\n" if ($comment);
    $aclhash{$name}=1;

    my @rules;
    db_query("SELECT c.id,c.mode,c.ip,c.acl,c.tkey,c.op,c.comment," .
	     " a.name,a.server,k.name " .
	     "FROM cidr_entries c LEFT JOIN acls a ON c.acl=a.id " .
	     " LEFT JOIN keys k ON c.tkey=k.id " .
	     "WHERE c.type=0 AND c.ref=$id ORDER BY c.id",\@rules);
    my $j;
    for $j (0..$#rules) {
      my($rid,$mode,$cidr,$acl,$tkey,$op,$comment,
	 $aclname,$aclref,$keyname) = @{$rules[$j]};

      if ($op == 1) { $op='!'; } else { $op=' '; }
      if ($comment) { $comment="\t// $comment"; }
      else { $comment=''; }

      if ($mode == 0 && $cidr) {
	$cidr =~ s/\/32$//;
	printf BINDFILE "\t%1s %-20s %s\n",$op,"$cidr;",$comment;
      } elsif ($mode == 1 && $acl > 0) {
	if (($acl >= $id || not $aclhash{$aclname}) && $aclref != -1) {
	  error("Skipping invalid reference to ACL $aclname in ACL: $name");
	} elsif ($aclname) {
	  printf BINDFILE "\t%1s %-20s %s\n",$op,"$aclname;",$comment;
	} else {
	  error("Skipping missing ACL reference ($acl) in ACL: $name");
	}
      } elsif ($mode == 2 && $tkey > 0) {
	unless ($keyhash{$tkey}) {
	  error("Skipping reference to unknown key: $keyname (id=$tkey) " .
		"in ACL: $name");
	} else {
	  printf BINDFILE "\t%1s %-20s %s\n",$op,"key $keyname;",$comment;
	}
      } else {
	error("Invalid entry (id=$rid) in acl: $name");
      }
    }
    
    print BINDFILE "};\n\n";
  }

}

######################################################################
# printcap

sub make_printcap() {
  print "PRINTER configuration\n";
  $time_now = localtime;

  # printcap
  print "Generating printcap...\n";

  print "printcap generation not yet implemented!\n";
  return;

  open(PCAPFILE,">printcap") || fatal("Cannot create printcap!");
  print PCAPFILE "# printcap -- automagically generated by Sauron v$VER\n";
  print PCAPFILE "#             created by $user at $time_now\n#\n";


  # FIXME .................................!!!!!!!!!!

  print PCAPFILE "# eof\n";
  close(PCAPFILE);
}



##########################################################################
# main program

$VER = sauron_version();

$result=GetOptions("help|h","all|a","bind|b","dhcp|d","dhcp6","printer|p","mail",
		   "updateserial","noupdateserial","verbose","dhcp2","clean",
		   "check","dhcpclass=s","dhcpvlans=s","tinydns|t", "ignorelocal|i");

if ($opt_help || @ARGV < 1 || $result < 1) {
    print "syntax: $0 [--help] [options] <servername> [<target directory>]\n";
    print "\n\toptions:\n",
      "\t--all                generate all configuration files\n",
      "\t--bind               generate BIND (named) configuration files\n",
      "\t--tinydns            generate DJBDNS (tinydns) configuration files\n",
      "\t--dhcp               generate DHCP (dhcpd) configuration files\n",
      "\t--dhcp6              generate DHCPv6 (dhcpd) configuration files\n",
      "\t--clean              cleanup expired records and vacuum database\n",
      "\t--printer            generate PRINTER (lpd) configuration files\n\n",
      "\t--updateserial       force serial update on master zones\n\n",
      "\t--ignorelocal        force ignore RFC1918 subnets\n\n",
      "\t--check              check validity of generated dhcp.conf,named.conf,\n",
      "\t                     and zone files\n\n",
      "\t--dhcp2              enable DHCPv2 compatibilty mode\n",
      "\t--dhcpclass=<name>   generate only <name>.class file that contains\n",
      "\t\t\t     given dhcp class declaration\n",
#     "\t--dhcpvlans=<regexp> generate DHCP configuration for given VLANs\n\n",
      "\t--mail               enable email notification sending\n\n";
    print "" if ($opt_help);
    exit(1);
}

$opt_verbose=($opt_verbose ? 1 : 0);
$opt_clean=($opt_clean ? 1 : 0);
$opt_dhcp=($opt_dhcp ? 1 : 0);
$opt_dhcp6=($opt_dhcp6 ? 1 : 0);
$opt_bind=($opt_bind ? 1 : 0);
$opt_tinydns=($opt_tinydns ? 1 : 0);
$opt_printer=($opt_printer ? 1 : 0);
$opt_dhcp2=($opt_dhcp2 ? 1 : 0);
$opt_updateserial=($opt_updateserial ? 1 : 0);
$opt_noupdateserial=($opt_noupdateserial ? 1 : 0);
$opt_check=($opt_check ? 1 : 0);
$opt_ign_local = ($opt_ignorelocal ? 1 : 0);
$dhcp6_conf=1 if ($opt_dhcp6 || $opt_all);
$dhcp_conf=1 if ($opt_dhcp || $opt_all);
$bind_conf=1 if ($opt_bind || $opt_all);
$tiny_conf=1 if ($opt_tinydns || $opt_all);
$printer_conf=1 if ($opt_printer || $opt_all);
$dhcp2_mode=1 if ($opt_dhcp2);

$servername = $ARGV[0];
if (@ARGV > 1) {
	$targetdir = $ARGV[1];
	$targetdir .= "/" unless ($targetdir =~ /\/$/);
} else {
	$targetdir = "./";
}
$user = (getpwuid($<))[0];
$host = `hostname`;
$host =~ s/\n//g;


fatal("target directory is a file! ($targetdir)") if (-f $targetdir);
fatal("target directory does not exists ($targetdir)") if (! -d $targetdir);
fatal("no permissions to target directory!") 
	unless (-r $targetdir && -x $targetdir && -w $targetdir);
fatal("--check option used but SAURON_NAMED_CHK_PROG not configured!")
  if ($opt_check && $bind_conf && ! $SAURON_NAMED_CHK_PROG);
fatal("--check option used but SAURON_ZONE_CHK_PROG not configured!")
  if ($opt_check && $bind_conf && ! $SAURON_ZONE_CHK_PROG);
fatal("--check option used but SAURON_DHCP_CHK_PROG not configured!")
  if ($opt_check && $dhcp_conf && ! $SAURON_DHCP_CHK_PROG);
fatal("--check option used but SAURON_DHCP6_CHK_PROG not configured!")
  if ($opt_check && $dhcp6_conf && ! $SAURON_DHCP6_CHK_PROG);
fatal("cannot find/execute SAURON_NAMED_CHK_PROG: $SAURON_NAMED_CHK_PROG")
  if ($opt_check && $bind_conf && ! -x $SAURON_NAMED_CHK_PROG);
fatal("cannot find/execute SAURON_ZONE_CHK_PROG: $SAURON_ZONE_CHK_PROG")
  if ($opt_check && $bind_conf && ! -x $SAURON_ZONE_CHK_PROG);
fatal("cannot find/execute SAURON_DHCP_CHK_PROG: $SAURON_DHCP_CHK_PROG")
  if ($opt_check && $dhcp_conf && ! -x $SAURON_DHCP_CHK_PROG);
fatal("cannot find/execute SAURON_DHCP6_CHK_PROG: $SAURON_DHCP6_CHK_PROG")
  if ($opt_check && $dhcp6_conf && ! -x $SAURON_DHCP6_CHK_PROG);
fatal("--dhcpclass can only be used with --dhcp option")
  if ($opt_dhcpclass && not $opt_dhcp);
fatal("--dhcpvlans can only be used with --dhcp option")
  if ($opt_dhcpvlans && not $opt_dhcp);

print "server name: $servername\n",
      "target directory: $targetdir\n" if ($opt_verbose);

print "DHCP v2 compatibility mode enabled\n" if ($dhcp2_mode);
chdir($targetdir) || fatal("Cannot change to directory '$targetdir'!");

$mailnotify_mode = ($opt_mail ? 1 : 0);
$mailnotify_mode = 0 unless ($SAURON_MAILER && -x $SAURON_MAILER);
$mailnotify_mode = 0 unless ($SAURON_MAIL_FROM);
error("mailer settings missing from config")
  if ($opt_mail && $mailnotify_mode == 0);
print "Email notification sending: " .
      ($mailnotify_mode ? 'enabled':'disabled') . "\n" if ($opt_verbose);

db_connect();

$time_now_ticks = time;


$serverid = get_server_id($servername);
fatal("Cannot find server '$servername' from database.") if ($serverid < 0);
fatal("Cannot get server record!") if (get_server($serverid,\%server));

error("Primary zone-file path is not relative pathname " .
      "($server{pzone_path})!") if ($server{pzone_path} =~ /^\//);

$dhcp_auto_domainnames = $server{dhcp_flags_ad};
$failover_mode = ($server{dhcp_flags_fo} ? 1 : 0);

$zonelist = get_zone_list($serverid,0,0);
undef %zonelist_hash;
foreach $zone (@{$zonelist}) { $zonelist_hash{$$zone[0]}=$zone; }

$normalzones = @{$zonelist};
if ($server{masterserver} > 0) {
  print "this server is slave (for id=$server{masterserver})\n"
    if ($opt_verbose);
  fatal("Cannot get master server record!")
    if (get_server($server{masterserver},\%masterserver));
  push @{master_server_ips}, [$masterserver{hostaddr}];

  $zonelist2 = get_zone_list($server{masterserver},0,0);
  # add selected zones from master server to slave...
  foreach $zone (@{$zonelist2}) {
    #print "zone: $$zone[0]\n";
    if ($zonelist_hash{$$zone[0]}) {
      print "skipping duplicate zone from master ($$zone[0])\n"	
	if ($opt_verbose);
      next;
    }
    next unless ($$zone[2] =~ /^[MSF]$/);
    push @{$zonelist}, $zone;
  }

  $dhcp_auto_domainnames = $masterserver{dhcp_flags_ad};

  # disable dynamic addresses unless failover protocol is enabled...
  if ($masterserver{dhcp_flags_fo}) { $failover_mode=2; }
  else {
    $dhcp2_mode=1;
    print "disabling dynamic pools for slave\n";
  }
}
$zones = @{$zonelist};

print "Server '$servername' has $zones zones.\n";

clean_up() if ($opt_clean);
make_dns() if ($bind_conf || $tiny_conf);
make_printcap() if ($printer_conf);
make_dhcp() if ($dhcp_conf);
make_dhcp6() if ($dhcp6_conf);

exit(0);


# eof
