#!/usr/bin/perl -w

use strict;
use XML::LibXML;
use Digest::MD5 qw(md5_hex);
use POSIX qw(strftime);
use Fcntl qw(:DEFAULT :flock);

$| = 1;

sub mk_new_master_serial($) {
	my $serials = shift;
	
	my $master_el = ($serials->getDocumentElement()->findnodes("/serials/master"))[0];
	my $master_date = $master_el->getAttribute('date');
	my $master_count = $master_el->getAttribute('count');
	my $date = strftime('%Y%m%d', localtime);
	my $count = $master_count + 1;
	
	die 'no more serials for today!' if ($count >= 100);
	
	$count = 0 if ($master_date ne $date);
	
	$master_el->setAttribute('date', $date);
	$master_el->setAttribute('count', $count);
	
	return sprintf('%s%02d', $date, $count);
}

if ($> == 0) {
	exec "su", "ganymede", "-c", $0;
	exit 1;
}

my $parser = XML::LibXML->new();

my $xml_source = '/var/cache/xsltzone/ganymede-data.xml';
my $xml_zones  = '/var/cache/xsltzone/zones.xml';
my $xml_serial = '/var/lib/xsltzone/serials.xml';
my $xsl_dns    = '/usr/share/xsltzone/create-dns.xsl';
my $dnsdir     = '/var/cache/xsltzone/dns';
my $lockfile   = '/var/lock/xsltzone';

open(LOCKFILE, '>', $lockfile) or die "$0: can't open lockfile: $lockfile\n";
if (flock(LOCKFILE, LOCK_EX) == -1) {
	die "$0: can't get lock on $lockfile\n";
}

my $start;
printf "dumping ganymede data..."; $start = time;
system('/etc/xsltzone/dump-data');
printf " %ds\n", time - $start;
printf "generating dns data..."; $start = time;
system('xalan', -xsl => $xsl_dns, -in => $xml_source, -out => $xml_zones);
printf " %ds\n", time - $start;
-x '/etc/xsltzone/xmlhook' and system('/etc/xsltzone/xmlhook', $xml_zones);

my $tree = $parser->parse_file($xml_zones);
my $serials;
if (-e $xml_serial) {
	$serials = $parser->parse_file($xml_serial);
} else {
	$serials = $parser->parse_string(<<EOF);
<?xml version="1.0" encoding="UTF-8"?>
<serials>
  <master date="0" count="0"/>
</serials>
EOF
}
my $root = $tree->getDocumentElement();

my $new_master_serial;
my @zones  = $root->findnodes('/dns/zone');
my @nameds = $root->findnodes('/dns/named-conf');
my @tsig   = $root->findnodes('/dns/tsig-conf');

mkdir $dnsdir unless -e $dnsdir;

foreach my $named_el (@nameds) {
	my $named = $named_el->findvalue('@id');
	printf "writing config for $named\n";
	
	open(OUT, '>', $dnsdir."/named.conf.$named");
	print OUT $named_el->textContent;
	close(OUT);
}

foreach my $tsig_el (@tsig) {
	printf "writing tsig config\n";
	
	open(OUT, '>', $dnsdir."/tsig.conf");
	print OUT $tsig_el->textContent;
	close(OUT);
}

my %all_current_zone_files;
foreach my $zone_el (@zones) {
	my $zone_fqdn = $zone_el->findvalue('@id');
	my $zone_md5 = md5_hex($zone_el->textContent());
	my $zone_file = $dnsdir."/db.".$zone_fqdn;
	$all_current_zone_files{$zone_file} = $zone_file;
	
	my $ref_serial_el = ($serials->getDocumentElement()->findnodes("/serials/zone[\@id = '$zone_fqdn']"))[0];
	unless (defined($ref_serial_el)) {
		$ref_serial_el = $serials->createElement('zone');
		$ref_serial_el->setAttribute('serial', '');
		$ref_serial_el->setAttribute('md5sum', '');
		$ref_serial_el->setAttribute('id', $zone_fqdn);
		($serials->getDocumentElement()->findnodes("/serials"))[0]->appendChild($serials->createTextNode('  '));
		($serials->getDocumentElement()->findnodes("/serials"))[0]->appendChild($ref_serial_el);
		($serials->getDocumentElement()->findnodes("/serials"))[0]->appendChild($serials->createTextNode("\n"));
	}
	
	my $ref_md5 = $ref_serial_el->getAttribute('md5sum');
	my $changed = $ref_md5 ne $zone_md5 ? 1 : 0;
	#printf "zone: %-40s %s\n", $zone_fqdn, ($changed ? "changed!" : "not changed");
	next unless $changed;
	printf "changed zone: %s\n", $zone_fqdn;
	
	$new_master_serial = mk_new_master_serial($serials) unless defined($new_master_serial);
	my $serial_el = ($zone_el->findnodes('serial'))[0];
	$serial_el->appendChild($serials->createTextNode($new_master_serial));
	$ref_serial_el->setAttribute('md5sum', $zone_md5);
	$ref_serial_el->setAttribute('serial', $new_master_serial);
	
	open(OUT, '>', $zone_file);
	print OUT $zone_el->textContent;
	close(OUT);
}

foreach my $file (glob $dnsdir."/db.*") {
	next if exists $all_current_zone_files{$file};
	printf "obsolete file: %s\n", $file;
	unlink $file;
}

$serials->toFile($xml_serial, 0) if defined($new_master_serial);

foreach my $named_el (@nameds) {
	my $named = $named_el->findvalue('@id');
	my @dbfiles = map {$_->findvalue('@file')} $named_el->findnodes('dbfile');
	
	system("/etc/xsltzone/sync-ns", $named, @dbfiles);
}

flock(LOCKFILE, LOCK_UN);

exit 0;
1;
