use utf8;
use warnings; no warnings "redefine";
use strict;
use utf8;

use Net::DNS;
use Net::DNS::Update;
use Cf qw($NSI $NSI_KEY_NAME $NSI_KEY);
use Fehler qw(problem warnung);
use Dbase::Globals qw(aufzaehlung);
use Dbase::Help qw(DoFn);
use Loader qw(domain_whois get_rcode_info);
use Dbase::IP;
use Term::ANSIColor qw(GREEN RED RESET);

my $res = new Net::DNS::Resolver;
$res->nameservers($NSI) unless $ENV{'TESTING3'};

my $find_zone_for = sub {
	my $fqdn = my $zone = shift;
	do {
		defined( my $query = $res->send( $zone, 'SOA' ) )
		  or return problem(
			"Fehler bei der SOA-Abfrage für $zone:" . $res->errorstring );

		# Folgender Abschnitt wurde anlässlich Ticket 10118405 eingebaut.
		# Es ist noch nicht sicher, ob das der Weisheit letzter Schluss ist,
		# aber es löst zumindest das konkrete Problem:
		if ( $query->header->rcode eq 'REFUSED' ) {
			my $res =
			  Net::DNS::Resolver->new;  # mit "normalen" DNS-Servern, nicht dns0
			defined( my $packet = $res->send( $zone, 'SOA' ) )
			  or return problem("Fehler bei der NS-Abfrage für $zone.");
			my ($soa) = grep $_->type eq 'SOA', $packet->answer,
			  $packet->authority;
			warnung(
				"$zone wird wohl nicht bei uns verwaltet"
				  . (
					defined $soa && ",\nsondern mutmaßlich auf " . $soa->mname
				  )
				  . ";\ndiesen Eintrag kann ich also nicht ändern."
			);
			return;
		}

		return $zone
		  if $query->answer and ( $query->answer )[0]->type eq 'SOA';

		if ( my @other_ns = grep $_->type eq 'NS', $query->authority ) {

			# Falls es eine Sub-Zone zu einer bei uns
			# verwalteten Zone gibt (z. B. nis.noris.de zu
			# noris.de), können wir hier zumindest die
			# maßgeblichen NS ausgeben; bei einer komplett
			# fremden Domain erhalten wir von dns0 hingegen
			# nur einen Verweis auf die .-Zone:
			my $name = $other_ns[0]->name;
			return warnung(
				"Der Name $zone wird nicht auf $NSI verwaltet"
				  . (
					length $name > 0
					  && ";\noffenbar gibt es für $name eine DNS-Zone auf "
					  . aufzaehlung( sort map $_->nsdname, @other_ns )
				  )
				  . '.'
			);
		}

	} while $zone =~ s/^.+?\.//;

	problem("Kein SOA für $fqdn gefunden!\n")
	  unless $ENV{'TESTING2'};
};

my $send_dns_update = sub {
	my ( $fqdn, $zone, $set, $description, $type, %rr ) = @_;
	my $packet = Net::DNS::Update->new($zone);

	$packet->push(
		pre => Net::DNS::RR->new(
			Name  => $fqdn,
			TTL   => 0,
			Class => $set ? 'NONE' : ( 'IN', %rr ),
			Type  => $type,
		)
	);
	$packet->push(
		update => Net::DNS::RR->new(
			Name  => $fqdn,
			Class => 'ANY',
			Type  => $type,
		)
	);
	$packet->push(
		update => Net::DNS::RR->new(
			Name  => $fqdn,
			TTL   => 86400,
			Class => 'IN',
			Type  => $type,
			%rr,
		)
	) if $set;

	$packet->sign_tsig( $NSI_KEY_NAME, $NSI_KEY )
	  if $NSI_KEY_NAME && $NSI_KEY;

	print $set ? 'Erzeuge' : 'Lösche',
	  " via $NSI $description-Eintrag für $fqdn:\n => ";
	my $ans = $res->send($packet);

	if ( defined $ans ) {
		my $rcode_info = get_rcode_info( my $rcode = $ans->header->rcode );
		print $rcode eq 'NOERROR' ? GREEN : RED, $rcode,
		  defined $rcode_info && " ($rcode_info)", RESET, "\n";
	}
	else {
		print 'FEHLER: ', $res->errorstring, "\n";
	}
};

sub serve_ipnr($$;$) {
	return if $ENV{'TESTING3'};
	my($idi,$set,$kn) = @_;

	my ($text,$ipnr,$bits) = DoFn("select name,ip6,bits from ipkunde where id=$idi");
	$ipnr = Dbase::IP->new_db($ipnr,$bits);

	if ( !$set && $bits ) { # Beendigung eines IP-Adressbereichs

		my $whois = join '',
		  domain_whois( $ipnr->str,
			    'whois.ripe.net -B -r -T inet'
			  . ( !$ipnr->is_v4 && '6' )
			  . 'num <domain-ace>' )
		  or return;

		if ( $whois =~ /^status:\s+ASSIGNED\b/mi ) {
			$whois =~ y/\cM//d;
			$whois =~ /^(inet6?num:.*?)\n\n/msi
			  or return problem "whois-Ausgabe von RIPE hat unerwartetes Format:\n$whois";
			warnung "Bereich ist laut RIPE-DB noch assigned:\n\n$1";
		}
	}

	my $type;
	if(defined $bits and $bits == 0) {
		$type = $ipnr->is_v4 ? "A" : "AAAA";
	} else {
		return undef; ## XXX TODO: INFO/PTR-Record o.ä. anlegen
	}

	return undef if not defined $text;
	my $addr = $ipnr->addr;

# Set/clear name->ip
	if ( defined( my $zone = $find_zone_for->($text) ) ) {
		$send_dns_update->( $text, $zone, $set, $type, $type,
			Address => $addr );

		$send_dns_update->(
			$text, $zone, $set, 'VRF-A-', 'A', Address => $ipnr->str4
		) if $ipnr->is_v4rf > 1;
	}

# Set/clear ip->name
	my $rptr = $ipnr->revstr;
	if ( my $rzone = $res->query( $rptr, 'CNAME' ) ) {
		$rptr = ( $rzone->answer )[0]->cname;
	}
		
	if ( defined( my $rzone = $find_zone_for->($rptr) ) ) {
		$send_dns_update->(
			$rptr, $rzone, $set, 'PTR', 'PTR', PTRdname => $text
		);
	}
}

1;
