#!/usr/bin/perl -w
BEGIN {
	my $x = $0; $x =~ s/\/[^\/]+$//;
	if ($x eq $0 || $x eq '') { $x = `pwd`;chomp $x; };
	require "$x/config.pl";
};

use strict ;
use Net::DNS;

sub usage {
	print "usage: secondary_zone ns zone ...\n"; exit 1;
}


sub fix_domain ($$) {
	my ($base, $zn)=(@_);
	return $base if $zn eq '';
	return $zn;
}

sub relative_name ($$) {
	my ($nm, $zn) = @_;

	if ($ENV{RELATIVE_NAMES}) {
		return lc $nm unless $nm =~ /\./;
		return    $nm     if $nm =~ /^\d+\.\d+\.\d+\.\d+$/;
		return lc $1      if $nm =~ /^(.*)\.$zn\.?$/;
		return lc $nm     if $nm =~ /\.$/;
		return lc "$nm." ;
	} else {
		if ($nm =~ s/\.$//) {
			return lc $nm;
		} else {
			my $x = lc($nm . $zn);
			$x =~ s/\.$//;
			return $x;
		}
	}
}

sub given_serial ($$) {
	my ($ldap, $dn) = @_ ;
	my $attr = 'sOARecord' ;
	my $mesg = $ldap->search
		(base   => dn_domain ($dn),
		 attrs  => $attr,
		 scope  => 'base',
		 filter => '(objectClass=dnsDomain)');
	return 0 if $mesg->code ;
	my @entry = $mesg->entries ;
	my $entry = $entry[0] ;
	my $value = $entry->get_value ($attr) ;
	return 0 unless defined $value ;
	return $1 if $value =~ /^(\d+)/ ;
	return 0 ;
}

sub delete_zone ($$) {
	my ($ldap, $rdn) = @_ ;
	my $dn   = dn_domain ($rdn) ;
	my $mesg = $ldap->search
		(base   => $dn,
		 scope  => 'one',
		 filter => '(objectClass=dnsDomain)');
	return 0 if $mesg->code ;

	foreach my $entry ($mesg->all_entries) {
		$ldap->delete ($entry) ;
		next unless $mesg->code ;
		print "Cannot delete \"", $entry->dn, "\" (", $mesg->error, ")\n";
	}
	$ldap->delete ($dn) ;
	return 1 unless $mesg->code ;
	print "Cannot delete \"$dn\" (", $mesg->error, ")\n";
	return 1 ;
}

my $ns1ip = $ENV{IP};
my @local_ns = ();

warn "IP not set; won't force creation of working nameserver records"
	unless defined ($ns1ip) || $ARGV[1] eq '.' ;	# root zones...

if ($ns1ip && $ns1ip ne '') {
	my $x = $ENV{LOCAL_NS};
	if ($x && $x ne '') {
		@local_ns = split /\s*,\s*/, $x;
	}
	if ($ns1ip =~ /,/) {
		# tada
		my @x = split /\s*\,\s*/, $ns1ip;
		$ns1ip = \@x;
	}
}

my $ns   = shift @ARGV; usage() unless defined $ns;
my @zone =       @ARGV; usage() unless $#zone >= 0;

if ($zone[0] eq '-') {
	@zone = ();
	while (<STDIN>) { chomp; push (@zone, $_); }
}

my $ldap = &get_ldap_conn;
my $res  = new Net::DNS::Resolver;
$res->nameservers ($ns);

read_zone:
foreach my $zonename (@zone) {

	my @records = $res->axfr ($zonename);
	while (!@records) {
		if ($res->errorstring ne "couldn't connect") {
			print "Skipping zone $zonename (could not connect to name server).\n" ;
			next read_zone ;
		}
		sleep (10);
		@records = $res->axfr ($zonename);
	}

	foreach my $rr (@records) {
		### $rr->print;

		die "Invalid ", $rr->type, " record for ", $rr->name, "  "
			unless ($rr->name  =~ /^(\*\.)?[\w.\/+-]+$/ &&
				$rr->ttl   =~ /^\d+$/               &&
				$rr->class =~ /^in$/i               &&
				$rr->type  =~ /^[a-z]+$/i           );

		my $zname = fix_domain ($zonename, $rr->name) ;
		my $dcdom = dc_domain ($rr->name) ;

		if (lc $rr->type eq 'soa') {
			die "Invalid SOA fields for ", $zonename, " "
				unless ($rr->serial  =~ /^\d+$/ &&
					$rr->refresh =~ /^\d+$/ &&
					$rr->retry   =~ /^\d+$/ &&
					$rr->expire  =~ /^\d+$/ &&
					$rr->minimum =~ /^\d+$/ &&
					$rr->rname   =~ /^[\w.+-]+$/);

			# check whether something has changed, at all
			if (given_serial ($ldap, $zname) >= $rr->serial) {
				print "Skipping zone $zonename (serial has not changed).\n" ;
				next read_zone;
			}
			if (delete_zone ($ldap, $zname)) {
				print "Replacing zone $zonename.\n" ;
			} else {
				print "Adding zone $zonename.\n" ;
			}

			my $soarecord = $rr->serial
				      . " "
				. $rr->refresh
				. " "
				. $rr->retry
				. " "
				. $rr->expire
				. " "
				. $rr->minimum
				;

			set_record ($ldap, $zname,
			[dc          => $dcdom,
			 objectClass => 'dnsDomain',
			 objectClass => 'dcObject',
			 objectClass => 'inetOrgPerson',
			 objectClass => 'domain',
			 nSRecord    => relative_name ($rr->mname, $zonename),
			 mail		=> relative_name ($rr->rname, $zonename),
			 sOARecord   => $soarecord],
			{sOARecord   => $soarecord});
			next ;
		}

		if (lc $rr->type eq "a") {
			die "Invalid ", $rr->type, " record for ", $rr->name, "  "
			unless ($rr->address =~ /^\d+\.\d+\.\d+\.\d+$/);

			next if lc ($rr->name) eq lc ("localhost.$zonename");
			add_record ($ldap, $zname,
			[dc          => $dcdom,
			 objectClass => 'dnsDomain',
			 objectClass => 'dcObject',
			 objectClass => 'domain',
			 aRecord     => $rr->address],
			{aRecord     => $rr->address});
			next ;
		}

		if (lc $rr->type eq "mx") {
			die "Invalid ", $rr->type, " record for ", $rr->name, "  "
			unless ($rr->preference =~ /^\d+$/ &&
				$rr->exchange   =~ /^[\w.+-]+$/);

			my $name = $rr->preference . " " .
			relative_name ($rr->exchange, $zonename) ;
			add_record ($ldap, $zname,
			[dc          => $dcdom,
			 objectClass => 'dnsDomain',
			 objectClass => 'dcObject',
			 objectClass => 'domain',
			 mXRecord    => $name],
			{mXRecord    => $name});
			next ;
		}

		if (lc $rr->type eq "ns") {
			die "Invalid ", $rr->type, " record for ", $rr->name, "  "
				unless $rr->nsdname =~ /^[\w.+-]+$/;

			my $name = relative_name ($rr->nsdname, $zonename);
			add_record ($ldap, $zname,
			[dc          => $dcdom,
			 objectClass => 'dnsDomain',
			 objectClass => 'dcObject',
			 objectClass => 'domain',
			 nSRecord    => $name],
			{nSRecord    => $name });
			next ;
		}

		if (lc $rr->type eq "cname") {
			die "Invalid ", $rr->type, " record for ", $rr->name, "  "
				unless ($rr->cname =~ /^[\w.+-]+$/);

			my $name = relative_name ($rr->cname, $zonename);
			add_record ($ldap, $zname,
			[dc          => $dcdom,
			 objectClass => 'dnsDomain',
			 objectClass => 'dcObject',
			 objectClass => 'domain',
			 cNAMERecord => $name],
			{cNAMERecord => $name});
			next;
		}

		if (lc $rr->type eq "txt") {
			# displays as "doo" "doo" ""
			my $arg = join('|', (split /\" \"/, substr ($rr->txtdata, 1, -1)));
			$arg =~ s/\|$//;

			set_record ($ldap,$zname,
			[dc          => $dcdom,
			 objectClass => 'dnsDomain',
			 objectClass => 'dcObject',
			 objectClass => 'domain',
			 description => $arg],
			{description => $arg});
			next ;
		}

		if (lc $rr->type eq "ptr") {
			die "Invalid PTR record for ", $rr->name, "  "
				unless $rr->ptrdname =~ /^[\w.\/+-]+$/;

			my $name = relative_name ($rr->ptrdname, $zonename);
			add_record ($ldap, $zname,
			[dc          => $dcdom,
			 objectClass => 'dnsDomain',
			 objectClass => 'dcObject',
			 objectClass => 'domain',
			 cNAMERecord   => $name],
			{cNAMERecord   => $name});
			next ;
		}
	}
}

