DNS-nsdiff
view release on metacpan or search on metacpan
wail "loading zone $zone from file $file" unless $quiet;
return cleanzone qx{$compilezone -j $zone '$file' $quiet};
}
sub mname {
my $zone = shift;
my @soa = split ' ', qx{dig +short soa $zone};
my $primary = $soa[0];
fail "could not get SOA record for $zone"
unless defined $primary and $primary =~ m{^$hostname$};
return $primary;
}
my ($soa,$old) = (@ARGV < 2)
? axfrzone $zone, $opt{s} || mname $zone
: loadzone $zone, shift;
my ($newsoa,$new) = (@ARGV < 1)
? axfrzone $zone, $opt{m}
: loadzone $zone, shift;
# Does the SOA need to be updated?
$soa =~ $soare;
my $oldserial = $3;
my $oldsoa = "$1 $2 $4";
$newsoa =~ $soare;
my $newserial = $3;
my $upsoa = $oldsoa ne "$1 $2 $4"
|| ($soamode =~ m{file|master} && $oldserial < $newserial);
# The serial number in the update might depend on the new SOA serial number.
my $soamin = $soafun->($newserial);
# Remove unchanged RRs, and save each name's deletions and additions.
my (%del,%add,%uc);
map { $uc{lc $_} = $_ } keys %$new if $opt{c};
for my $rr (keys %$old) {
delete $old->{$rr};
next if $uc{lc $rr} and delete $new->{delete $uc{lc $rr}};
next if delete $new->{$rr};
my ($owner,$ttl,$data) = split ' ', $rr, 3;
push @{$del{$owner}}, $data;
}
for my $rr (keys %$new) {
delete $new->{$rr};
my ($owner,$data) = split ' ', $rr, 2;
push @{$add{$owner}}, $data;
}
# For each owner name prepare deletion commands followed by addition
# commands. This ensures TTL adjustments and CNAME/other replacements
# are handled correctly. Ensure each owner's changes are not split below.
my (@batch,@script);
sub emit {
if ($opt{0}) { push @script, splice @batch }
else { push @script, join '', splice @batch }
}
sub update {
my ($addel,$owner,$rrs) = @_;
push @batch, map "update $addel $owner $_", sort @$rrs;
}
for my $owner (keys %del) {
update 'delete', $owner, delete $del{$owner};
update 'add', $owner, delete $add{$owner} if exists $add{$owner};
emit;
}
for my $owner (keys %add) {
update 'add', $owner, delete $add{$owner};
emit;
}
my $status = ($upsoa or @script) ? 1 : 0;
if ($quiet) {
wail "$zone has changes" if $status;
exit $status;
}
# Emit commands in batches that fit within the 64 KiB DNS packet limit
# assuming textual representation is not smaller than binary encoding.
# Use a prerequisite based on the SOA record to catch races.
my $maxlen = 65536;
while ($upsoa or @script) {
my ($length,$i) = (0,0);
$length += length $script[$i++] while $length < $maxlen and $i < @script;
my @batch = splice @script, 0, $length < $maxlen ? $i : $i - 1;
fail "update does not fit in packet"
if not $upsoa and @batch == 0
or $opt{1} and @script != 0;
print "server $opt{s}\n" if $opt{u};
$soa =~ $soare;
print "prereq yxrrset $zone $2 $3 $4";
my $serial = $3 >= $soamin ? $3 + 1 : $soamin;
$newsoa =~ $soare;
print "update add ", $soa = "$zone $1 $2 $serial $4";
print @batch;
print "show\n" if $verbosity =~ m{q};
print "send\n";
print "answer\n" if $verbosity =~ m{r};
undef $upsoa;
}
exit $status;
__END__
=head1 NAME
nsdiff - create "nsupdate" script from DNS zone file differences
=head1 SYNOPSIS
nsdiff [B<-hV>] [B<-b> I<address>] [B<-k> I<keyfile>] [B<-y> [I<hmac>:]I<name>:I<key>]
[B<-0>|B<-1>] [B<-q>|B<-v> [q][r]] [B<-cCdD>] [B<-i> I<regex>] [B<-S> I<mode>|I<num>]
[B<-u>] [B<-s> I<server>] [B<-m> I<server>] <I<zone>> [I<old>] [I<new>]
=head1 DESCRIPTION
( run in 1.102 second using v1.01-cache-2.11-cpan-39bf76dae61 )