DNS-nsdiff-1.85/0000755000175000017500000000000014576605207011764 5ustar fanffanfDNS-nsdiff-1.85/MANIFEST0000644000175000017500000000037414576605207013121 0ustar fanffanfMANIFEST MANIFEST.SKIP Makefile.PL README.pod lib/DNS/nsdiff.pm nsdiff nspatch nsvi META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) DNS-nsdiff-1.85/nsvi0000755000175000017500000001377414576530741012705 0ustar fanffanf#!/usr/bin/perl # SPDX-License-Identifier: 0BSD OR MIT-0 use warnings; use strict; use File::Temp qw(tempfile); use Getopt::Std; use POSIX; sub wail { warn "nsvi: @_\n"; } sub fail { die "nsvi: @_\n"; } sub fale { die "nsvi: @_: $!\n"; } sub version { while () { print if m{^=head1 VERSION} ... m{^=head1 } and not m{^=head1 }; } exit; } sub usage { print STDERR < Transfer a zone from its server, edit it, then upload the edits using `nsdiff` and `nsupdate`. nsvi options: -h display full documentation -V display version information -n interactive confirmation -v turn on verbose output -01cCdD nsdiff options -S num|mode SOA serial number or mode -s server[#port] where to AXFR and UPDATE the zone -g use GSS-TSIG for UPDATE -k keyfile AXFR and UPDATE TSIG key -y [hmac:]name:key AXFR and UPDATE TSIG key EOF exit 1; } my %opt; usage unless getopts '-hV01cCdDgk:ns:S:vy:', \%opt; version if $opt{V}; exec "perldoc -oterm -F $0" if $opt{h}; usage if @ARGV != 1; my $zone = shift; $opt{v} = 'qr' if $opt{v}; my @dig = qw{dig +multiline +onesoa +nocmd +nostats +noadditional}; push @dig, map "-$_$opt{$_}", grep $opt{$_}, qw{k y}; if ($opt{s} and $opt{s} =~ m{^(.*)#(\d+)$}) { push @dig, "-p$2", "\@$1"; } elsif ($opt{s}) { push @dig, "\@$opt{s}"; } else { push @dig, "\@localhost"; } my @nsdiff = qw{nsdiff}; push @nsdiff, map "-$_", grep $opt{$_}, qw{0 1 c d D}; push @nsdiff, map "-$_$opt{$_}", grep $opt{$_}, qw{k s S v y}; push @nsdiff, "-slocalhost" unless $opt{s}; push @nsdiff, "-u" if $opt{s}; my @nsupdate = qw{nsupdate}; push @nsupdate, map "-$_", grep $opt{$_}, qw{g}; push @nsupdate, map "-$_$opt{$_}", grep $opt{$_}, qw{k y}; push @nsupdate, "-l" unless $opt{s}; my $secRRtypes = qr{NSEC|NSEC3|NSEC3PARAM|RRSIG}; $secRRtypes = qr{$secRRtypes|CDS|CDNSKEY} unless $opt{C}; $secRRtypes = qr{$secRRtypes|DNSKEY} unless $opt{D}; $secRRtypes = qr{$secRRtypes|DS} if $opt{d}; my $nl = qr{(?:;[^\n]*)?\n}; my $rdata = qr{(?:[^()\n]+ |(?:[(] (?:[^()\n]+|$nl)+ [)])+ )+$nl}x; my $dnssec = qr{(?m)^\S+\s+\d+\s+IN\s+($secRRtypes)\s+$rdata}; print "@dig axfr $zone" if $opt{v}; my $axfr = qx{@dig axfr $zone}; fail "failed to @dig axfr $zone" unless $axfr and $? == 0; $axfr =~ s{$dnssec}{}g; my ($fh,$fn) = tempfile("$zone.XXXXXXXXXX", TMPDIR => 1, UNLINK => 1); print $fh $axfr; close $fh; my $vi = $ENV{VISUAL} || $ENV{EDITOR} || "vi"; sub prompt { print shift; system "stty -icanon"; sysread STDIN, my $key, 1; system "stty icanon"; print "\n"; return $key; } sub retry { wail shift; my $key = prompt "re-edit and try again? (y/N) "; next RETRY if $key =~ m{[Yy]}; exit 1; } RETRY: for (;;) { system "$vi $fn"; fail "failed to $vi $fn" unless $? == 0; print "@nsdiff $zone $fn" if $opt{v}; my $diff = qx{@nsdiff $zone $fn}; if ($? == 0) { wail "no change"; exit 0; } retry "failed to @nsdiff $zone $fn" unless $diff and $? == 256; if ($opt{n}) { print "$diff\n"; my $key = prompt "make update, edit again, or quit? (u/e/Q) "; next RETRY if $key =~ m{[EeRr]}; exit 1 unless $key =~ m{[UuYy]}; } open my $ph, '|-', @nsupdate or retry "pipe to @nsupdate: $!"; print $ph $diff; last if close $ph; retry "pipe to @nsupdate: $!" if $!; retry "failed to @nsupdate"; } print "done\n" if $opt{v}; exit 0; __END__ =encoding utf8 =head1 NAME nsvi - transfer a zone, edit it, then upload the edits =head1 SYNOPSIS nsvi [B<-01cCdDghvV>] [B<-k> I] [B<-y> [I:]I:I] [B<-S> I|I] [B<-s> I] > =head1 DESCRIPTION The B program makes an AXFR request for the zone, runs your editor so you can make whatever changes you require, then it runs B | B to push those changes to the server. Automatically-maintained DNSSEC records are stripped from the zone before it is passed to your editor, and you do not need to manually adjust the SOA serial number. =head1 OPTIONS Most B options are passed to B and some to B. =over =item B<-h> Display this documentation. =item B<-V> Display version information. =item B<-v> Verbose mode. =item B<-n> Interactive confirmation. When you quit the editor, you will be shown the changes, then asked whether to make the update (press B or B), edit again (press B or B), or quit (press another key). =item B<-01cCdD> =item B<-S> B|I These options are passed to B. For details see the nsdiff manual. =item B<-s> I[#I] Transfer the zone from the server given in this option, and send the update request to the same place. You can specify the server host name or IP address, optionally followed by a "#" and the port number. If you do not use the B<-s> option, the zone will be transferred from I, and B will use B B<-l> to update the zone. =item B<-g> Passed to B to use GSS-TSIG for UPDATE. =item B<-k> I TSIG key file, passed to B, B, and B. =item B<-y> [I:]I:I Literal TSIG key, passed to B, B, and B. =back =head1 ENVIRONMENT =over =item B Location for temporary files. =item B =item B Which editor to use. C<$VISUAL> is used if it is set, otherwise C<$EDITOR>, otherwise B. =back =head1 VERSION This is nsvi-1.85 Written by Tony Finch at Cambridge University Information Services. You may do anything with this. It has no warranty. =head1 ACKNOWLEDGMENTS Thanks to Tristan Le Guern for the B<-n> option and Mantas Mikulėnas for the B<-g> option. Thanks to David McBride and Petr Menšík for providing useful feedback. =head1 SEE ALSO nsdiff(1), nsupdate(1), dig(1). =cut DNS-nsdiff-1.85/nsdiff0000755000175000017500000004376414576530741013201 0ustar fanffanf#!/usr/bin/perl # SPDX-License-Identifier: 0BSD OR MIT-0 use warnings; use strict; use Getopt::Std; use POSIX; sub wail { warn "nsdiff: @_\n"; } sub fail { wail @_; exit 2; } # for named-compilezone $ENV{PATH} .= ":/sbin:/usr/sbin:/usr/local/sbin"; my $compilezone = 'named-compilezone -i local -k warn -n warn -o -'; sub version { while () { print if m{^=head1 VERSION} ... m{^=head1 } and not m{^=head1 }; } exit; } sub usage { print STDERR < [old] [new] Generate an `nsupdate` script that changes a zone from the "old" version into the "new" version, ignoring DNSSEC records. If the "old" file is omitted and there is no -s option, `nsdiff` will AXFR the zone from the server in the zone's SOA MNAME field. options: -h display full documentation -V display version information -0 allow a domain's updates to span packets -1 abort if update doesn't fit in one packet -c compare records case-insensitively -C do not ignore CDS/CDNSKEY records -d ignore DS records -D do not ignore DNSKEY records -i regex ignore records matching the pattern -m server[#port] from where to AXFR new version of the zone -s server[#port] from where to AXFR old version of the zone -S num|mode SOA serial number or update mode -q only output if zones differ -u tell nsupdate to send to -s server -v [q][r] verbose query and/or reply -b address AXFR query source address -k keyfile AXFR query TSIG key -y [hmac:]name:key AXFR query TSIG key EOF exit 2; } my %opt; usage unless getopts '-hV01cCdDi:m:s:S:quv:b:k:y:', \%opt; version if $opt{V}; exec "perldoc -oterm -F $0" if $opt{h}; usage if @ARGV < 1 || @ARGV > 3; my @digopts; for my $o (qw{ b k y }) { push @digopts, "-$o $opt{$o}" if exists $opt{$o}; } wail "ignoring dig options when loading zones from files" if @digopts && @ARGV == 3; wail "ignoring -m option when loading new zone from file" if $opt{m} && @ARGV > 1; fail "need -m option when there are no input files" unless $opt{m} || @ARGV > 1; usage if $opt{u} && !$opt{s}; usage if $opt{q} && $opt{v}; usage if $opt{v} && $opt{v} !~ m{^[qr]*$}; my $quiet = $opt{q} ? '2>/dev/null' : ''; my $verbosity = exists $opt{v} ? $opt{v} : $quiet ? '' : 'r'; $opt{$_} and $opt{$_} =~ s{#}{ } for qw{ s m }; # for nsupdate server command my $secRRtypes = qr{NSEC|NSEC3|NSEC3PARAM|RRSIG}; $secRRtypes = qr{$secRRtypes|CDS|CDNSKEY} unless $opt{C}; $secRRtypes = qr{$secRRtypes|DNSKEY} unless $opt{D}; $secRRtypes = qr{$secRRtypes|DS} if $opt{d}; my $soamode = $opt{S} || 'file'; my $soafun = $soamode =~ m{^[0-9]+$} ? sub { return $soamode } : { serial => sub { return 0 }, file => sub { return $_[0] }, master => sub { return $_[0] }, # compat unix => sub { return time }, date => sub { return strftime "%Y%m%d00", gmtime }, }->{$soamode} or usage; my $zone = shift; $zone =~ s{[.]?$}{.}; my $zonere = quotemeta $zone; my $hostname = qr{(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?[.])+}; my $rname = qr{(?:[^;.\\\s]|\\.)+[.]$hostname|[.]}; my $soare = qr{^$zonere\s+(\d+)\s+(IN\s+SOA\s+$hostname\s+$rname) \s+(\d+)\s+(\d+\s+\d+\s+\d+\s+\d+\n)$}x; my $dnssec = qr{^\S+\s+\d+\s+IN\s+($secRRtypes)\s+}; my $exclude = $opt{i} ? qr{$dnssec|$opt{i}} : qr{$dnssec}; # Check there is a SOA and remove DNSSEC records. # Store zone data in the keys of a hash. sub cleanzone { my ($soa,%zone) = shift; fail "missing SOA record" unless defined $soa and $soa =~ $soare; $zone{$_} = 1 for grep { not m{^;|$exclude}o } @_; return ($soa,\%zone); } sub axfrzone { my $zone = shift; my $primary = shift; wail "loading zone $zone via AXFR from $primary" unless $quiet; $primary =~ s{^(.*) (\d+)$}{-p $2 \@$1} or $primary = '@'.$primary; return cleanzone qx{dig @digopts $primary +noadditional axfr $zone | $compilezone $zone /dev/stdin $quiet}; } sub loadzone { my ($zone,$file) = @_; 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
] [B<-k> I] [B<-y> [I:]I:I] [B<-0>|B<-1>] [B<-q>|B<-v> [q][r]] [B<-cCdD>] [B<-i> I] [B<-S> I|I] [B<-u>] [B<-s> I] [B<-m> I] > [I] [I] =head1 DESCRIPTION The B program examines the F and F versions of a DNS zone, and outputs the differences as a script for use by BIND's B program. It ignores DNSSEC-related differences, assuming that the name server has sole control over zone keys and signatures. The input files are typically in standard DNS zone file format. They are passed through BIND's B program to convert them to canonical form, so they may also be in BIND's "raw" format and may have F<.jnl> update journals. If the F file is not specified, B will use B to transfer the zone from the server given by the B<-s> option, or if the B<-s> option is missing it will get the server from the zone's SOA MNAME field. If both F and F files are not specified, B will transfer the new version of the zone from the server given by the B<-m> option. The SOA serial number has special handling: any difference between the F and F serial numbers is ignored (except in B<-S file> mode), because background DNSSEC signing activity can increment the serial number unpredictably. When the zones differ, B sets the serial number according to the B<-S> option, and it uses the F serial number to protect against conflicting updates. =head1 OPTIONS =over =item B<-h> Display this documentation. =item B<-V> Display version information. =item B<-0> Allow very large updates affecting one domain name to be split across multiple requests. =item B<-1> Abort if update does not fit in one request packet. =item B<-C> Do not ignore CDS or CDNSKEY records. They are normally managed by B with the C<-P sync> and C<-D sync> options, but you can use this option if you are managing them some other way. In that case, your un-signed zone file should include the complete CDS and/or CDNSKEY RRset(s); if not, B will delete the records. =item B<-c> Compare records case-insensitively. Can be helpful if the B target server does not preserve the case of domain names. However with this option, B does not correctly handle records that only differ in case. =item B<-D> Do not ignore DNSKEY records. It is sometimes necessary to take manual control over a zone's DNSKEY RRset, for instance to include a foreign DNSKEY records during migration to or from another hosting provider. If you use this option your un-signed zone file should include the complete DNSKEY RRset; if not, nsdiff will try to delete the DNSKEY records. Normally B will reject the update, unless the zone is configured with the I option. =item B<-d> Ignore DS records. This option is useful if you are managing secure delegations on the signing server (via nsupdate) rather than in the source zone. =item B<-i> I Ignore more DNS records. By default, B strips out DNSSEC RRs (except for DS) before comparing zones. You can exclude irrelevant changes from the diff by supplying a I that matches the unwanted RRs. =item B<-m> I[#I] Transfer the new version of the zone from the server given in this option, for example, a back-end hidden primary server. You can specify the server host name or IP address, optionally followed by a "#" and the port number. =item B<-s> I[#I] Transfer the old version of the zone from the server given in this option, using the same syntax as the B<-m> option. =item B<-S> B|B|B|B|I Choose the SOA serial number update mode: the default I takes the serial number from the I input zone; I uses a number of the form YYYYMMDDnn and allows for up to 100 updates per day; I just increments the serial number in the I input zone; I uses the UNIX "seconds since the epoch" value. You can also specify an explicit serial number value. In all cases, if the I input zone serial number is larger than the target value it is just incremented. Serial number wrap-around is not supported. =item B<-q> Quiet / quick check. Output is suppressed unless the zones differ, in which case a short note is printed instead of an B script. =item B<-u> Tell B to send the update message to the server specified in the B<-s> option. =item B<-v> [q][r] Control verbosity. The B flag causes queries to be printed. The B flag causes responses to be printed. To make B quiet, use S>. =back The following options are passed to B to modify its SOA and AXFR queries: =over =item B<-b> I
Source address for B queries =item B<-k> I TSIG key file for B queries. =item B<-y> [I:]I:I Literal TSIG key for B queries. =back =head1 EXIT STATUS The B utility returns 0 if the zones are the same, 1 if they differ, and 2 if there was an error. =head1 DIAGNOSTICS =over =item C =item CzoneE>> Errors in the command line. =item CzoneE>> Failed to retreive the zone's SOA using B when trying to obtain the server MNAME from which to AXFR the zone. =item C The output of B is incomplete, usually because the input file is erroneous. =item CzoneE> has changes> Printed instead of an B script when the B<-q> option is used. =item C The changes for one domain name did not fit in 64 KiB, or the B<-1> option was specified and all the changes did not fit in 64 KiB. =item C Warning emitted when the command line includes options for B as well as zone source files. =item C =item C The B<-m> I option is required when there are no file arguments, and ignored otherwise. =item CzoneE> via AXFR from I> =item CzoneE> from file I> Normal progress messages emitted before B invokes B, to explain the latter's diagnostics. =back =head1 EXAMPLE - DNSSEC It is easiest to deploy DNSSEC if you allow B to manage zone keys and signatures automatically, and feed in changes to zones using DNS update requests. However this is very different from the traditional way of manually maintaining zones in standard DNS zone file format. The B program bridges the gap between the two operational styles. To support this workflow you need BIND-9.7 or newer. You will continue maintaining your zone file C<$sourcefile> as before, but it is no longer the same as the C<$workingfile> used by B. After you make a change, instead of using C, run C. Configure your zone as follows, to support DNSSEC and local dynamic updates: zone $zone { type primary; file "$workingfile"; auto-dnssec maintain; update-policy local; }; To create DNSSEC keys for your zone, change to named's working directory and run these commands: dnssec-keygen -f KSK $zone dnssec-keygen $zone =head1 EXAMPLE - bump-in-the-wire signing A common arrangement for DNSSEC is to have a primary server that is oblivious to DNSSEC, a signing server which transfers the zone from the primary and adds the DNSSEC records, and a number of secondary servers which transfer the zone from the signer and which are the public authoritative servers. You can implement this with B, which handles the transfer of the zone from the primary to the signer. No modifications to the primary are necessary. You set up the signer as in the previous section. To transfer changes from the primary to the signer, run the following on the signer: nsdiff -m $primary -s $signer $zone | nsupdate -l =head1 EXAMPLE - dynamic reverse DNS You have a reverse zone such as C<2.0.192.in-addr.arpa> which is mostly managed dynamically by a DHCP server, but which also has some static records (for network equipment, say). You can maintain the static part in a DNS zone file and feed any changes into the live dynamic zone by telling B to ignore the dynamic entries. Say all the static equipment has IP addresses between 192.0.2.250 and 192.0.2.255, then you can run the command pipeline: nsdiff -i '^(?!25\d\.)' 2.0.192.in-addr.arpa 2.0.192.static | nsupdate -l =head1 CAVEATS By default B does not maintain the transactional semantics of native DNS update requests when the diff is big: it applies large changes in multiple update requests. To minimise the problems this may cause, B ensures each domain name's changes are all in the same update request. There is still a small risk of clients not seeing a change applied atomically when that matters (e.g. altering an MX and creating the new target in the same transaction). You can avoid the risk by using the B<-1> option to prevent multi-packet updates, or by being careful about changes that depend on multiple domain names. The update requests emitted by B include SOA serial number prerequisite checks to ensure that the zone has not changed while it is running. This can happen even in simple setups if B happens to be re-signing the zone at the time you make an update. Unfortunately the DNS update protocol does not allow for good error reporting when a prerequisite check fails. You can use B to cope with this problem. =head1 BUGS When updating a name's DNS records, B first deletes the old ones then adds the new ones. This ensures that CNAME replacements and TTL changes work correctly. However, this update strategy prevents you from replacing every record in a zone's apex NS RRset in one update, because it isn't possible to delete all a zone's name servers. =head1 VERSION This is nsdiff-1.85 Written by Tony Finch at Cambridge University Information Services. You may do anything with this. It has no warranty. =head1 ACKNOWLEDGMENTS Thanks to Athanasius, Mike Bristow, Piete Brooks (University of Cambridge Computer Laboratory), Terry Burton (University of Leicester), Owen Dunn (University of Cambridge Faculty of Mathematics), Martin Hartl (Barracuda), JP Mens, Mohamad Shidiq Purnama (PANDI), and Jordan Rieger (webnames.ca) for providing useful feedback. =head1 SEE ALSO nspatch(1), nsupdate(1), nsvi(1), dig(1), named(8), named-compilezone(8), perlre(1) =cut DNS-nsdiff-1.85/MANIFEST.SKIP0000644000175000017500000000010514576525533013661 0ustar fanffanf.git reversion.sh debian web Makefile$ MYMETA.* blib .*~ .*\.tar\..* DNS-nsdiff-1.85/META.yml0000644000175000017500000000117514576605207013241 0ustar fanffanf--- abstract: "create an 'nsupdate' script from DNS zone file differences" author: - 'Tony Finch ' build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010' license: unrestricted meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: DNS-nsdiff no_index: directory: - t - inc requires: perl: '5.010000' resources: repository: git://dotat.at/nsdiff.git version: '1.85' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' DNS-nsdiff-1.85/nspatch0000755000175000017500000001067214576530741013360 0ustar fanffanf#!/usr/bin/perl # SPDX-License-Identifier: 0BSD OR MIT-0 use warnings; use strict; use File::Temp qw(tempfile); use Getopt::Std; use POSIX; sub wail { warn "nspatch: @_\n"; } sub fail { die "nspatch: @_\n"; } sub fale { die "nspatch: @_: $!\n"; } sub version { while () { print if m{^=head1 VERSION} ... m{^=head1 } and not m{^=head1 }; } exit; } sub usage { print STDERR <; } sub dupout ($$) { no strict 'refs'; my ($dst,$src) = @_; open $dst, '>&', $src or fale 'dup'; } sub mktmp { return tempfile('nspatch.XXXXXXXXXX', TMPDIR => 1, UNLINK => 1); } sub tmpf { my $dup = shift; my ($fh,$name) = mktmp; dupout $dup, $fh; return $name; } dupout 'XSTDOUT', 'STDOUT'; dupout 'XSTDERR', 'STDERR'; sub runtmp { wail "running @_" if $opt{v}; my $out = tmpf 'STDOUT'; my $err = tmpf 'STDERR'; my $x = system @_; dupout 'STDOUT', 'XSTDOUT'; dupout 'STDERR', 'XSTDERR'; return ($out, $err, $x); } my ($dashh,$dash) = mktmp; print $dashh "----\n"; close $dashh; RETRY: for (;;) { my ($diffout,$differr,$diffx) = runtmp @nsdiff; system 'cat', $dash, $diffout, $dash, $differr, $dash if $opt{v} or ($diffx != 0 && $diffx != 256); exit 0 if $diffx == 0; fail "bad exit status from @nsdiff" if $diffx != 256; open STDIN, '<', $diffout or fale 'open'; my ($upout,$uperr,$upx) = runtmp @nsupdate; system 'cat', $dash, $upout, $dash, $uperr, $dash if $opt{v}; exit 0 if $upx == 0; if (slurp($uperr) eq "update failed: NXRRSET\n" and $opt{r}--) { wail "trying again" if $opt{v}; next RETRY; } else { system 'cat', $dash, $diffout, $dash, $differr, $dash, $upout, $dash, $uperr, $dash unless $opt{v}; fail "bad exit status from @nsupdate"; } } __END__ =head1 NAME nspatch - run `nsdiff | nsupdate` with error handling =head1 SYNOPSIS nspatch [B<-hVv>] [B<-r> I] -- [nsdiff options] -- [nsupdate options] =head1 DESCRIPTION The B utility runs `C` and checks that both programs complete successfully. It suppresses their output unless there is an error, in a manner suitable for running from B. The B script produced by B includes a prerequisite check to detect and fail if there is a concurrent update. These failures are detected by B which retries the update. Rather than using a pipe, B uses temporary files to store the output of B and B. =head1 OPTIONS =over =item B<-h> Display this documentation. =item B<-V> Display version information. =item B<-r> I If the update fails because of a concurrent update, B will retry up to I times. The default retry I is 2. =item B<-v> Turn on verbose mode, so the output from B and B is printed even if they are successful. (By default it is suppressed.) The verbose option is passed on to B. If B is not given the B<-v> option, it passes the B<-v ''> option to B. If B is given the B<-v> option, it passes the B<-v 'qr'> option to B. =back =head1 EXIT STATUS The B utility returns 0 if no change is required or if the update is successful, or 1 if there is an error. =head1 ENVIRONMENT =over =item C Location for temporary files. =back =head1 VERSION This is nspatch-1.85 Written by Tony Finch at Cambridge University Information Services. You may do anything with this. It has no warranty. =head1 SEE ALSO nsdiff(1), nsupdate(1), cron(8) =cut DNS-nsdiff-1.85/META.json0000644000175000017500000000216414576605207013410 0ustar fanffanf{ "abstract" : "create an 'nsupdate' script from DNS zone file differences", "author" : [ "Tony Finch " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "unrestricted" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "DNS-nsdiff", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "perl" : "5.010000" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "git://dotat.at/nsdiff.git", "web" : "https://dotat.at/cgi/git/nsdiff.git" } }, "version" : "1.85", "x_serialization_backend" : "JSON::PP version 4.04" } DNS-nsdiff-1.85/Makefile.PL0000644000175000017500000000332114576530741013735 0ustar fanffanf# SPDX-License-Identifier: 0BSD OR MIT-0 use 5.10.0; use ExtUtils::MakeMaker; # suppress warning about the ExtUtils::MakeMaker bug # that we fixed with the MAN3PODS setting below $SIG{__WARN__} = sub { print STDERR @_ unless "@_" eq "WARNING: Older versions of ExtUtils::MakeMaker may errantly install README.pod as part of this distribution. It is recommended to avoid using this path in CPAN modules.\n" }; WriteMakefile( NAME => 'DNS::nsdiff', VERSION => "1.85", ABSTRACT => "create an 'nsupdate' script from DNS zone file differences", EXE_FILES => [qw[ nsdiff nspatch nsvi ]], # to stop MakeMaker from installing the README as a man page # we have to list the section 3 pages explicitly MAN3PODS => { 'lib/DNS/nsdiff.pm' => 'blib/man3/DNS::nsdiff.3pm', }, AUTHOR => 'Tony Finch ', LICENSE => 'unrestricted', # cc0 not allowed by CPAN::Meta::Spec MIN_PERL_VERSION=> '5.10.0', META_MERGE => { 'meta-spec' => { version => 2 }, 'resources' => { repository => { type => 'git', url => 'git://dotat.at/nsdiff.git', web => 'https://dotat.at/cgi/git/nsdiff.git', } } } ); sub MY::postamble { return <<'MAKE_FRAG'; html:: mkdir -p web for f in nsdiff nspatch nsvi; \ do pod2html --noindex $$f >web/$$f.html; \ done pod2html --noindex README.pod >web/README.html ln -sf README.html web/index.html rm -f pod2htm?.tmp deb:: dpkg-buildpackage -uc -A mv ../nsdiff_* web/. debian/rules clean upload:: html git push --tags github trunk git push --tags chiark trunk if [ -f DNS-nsdiff-*.tar.gz ]; \ then mv DNS-nsdiff-*.tar.gz web/.; \ fi cp nsdiff web/. rsync -ilt web/nsdiff web/*.html web/*.tar.gz web/nsdiff_* \ chiark:public-html/prog/nsdiff/ MAKE_FRAG } DNS-nsdiff-1.85/README.pod0000644000175000017500000000601214576530741013424 0ustar fanffanf=head1 nsdiff =head2 create an "nsupdate" script from DNS zone file differences The B program examines the old and new versions of a DNS zone, and outputs the differences as a script for use by BIND's B program. It provides a bridge between static zone files and dynamic updates. The B script is a wrapper around C that checks and reports errors in a manner suitable for running from B. The B script makes it easy to edit a dynamic zone. =over I - JP Mens =back If you use BIND 9.7 or 9.8, you can use B as an alternative to the DNSSEC C feature which appeared in BIND 9.9. The server updates the DNSSEC records dynamically, but you can continue to manage the unsigned static zone file as before and use C<`nsdiff | nsupdate`> to push changes to the server. There are other situations where you have a zone which is partly dynamic and partly static, for example, a reverse DNS zone mostly updated by a DHCP server, which also has a few static entries. You can use B to update the static part of the zone. =head2 Dependencies To run nsdiff you need perl-5.10 or newer, and BIND version 9.7 or newer, specifically the B, B, and B utilities. =head2 Install To install, run: perl Makefile.PL make install To install in a particular place, use something like perl Makefile.pl PREFIX=${HOME} =head2 Downloads =over =item Documentation The nsdiff homepage is L Read the nsdiff manual: L Read the nspatch manual: L Read the nsvi manual: L =item Code Download the bare nsdiff perl source: L Download the source distribution: =over =item L =back =item Source repositories You can clone or browse the repository from: =over =item L =item L =item L =back =back =head2 Feedback Please send bug reports or patches to me at . Any contribution that you want included in `nsdiff` must be licensed under 0BSD and/or MIT-0, and must include a `Signed-off-by:` line to certify that you wrote it or otherwise have the right to pass it on as a open-source patch, according to the Developer's Certificate of Origin 1.1. =over =item 0BSD: L =item MIT-0 L =item DCO: L =back =head2 Licence Permission is hereby granted to use, copy, modify, and/or distribute this software for any purpose with or without fee. This software is provided 'as is', without warranty of any kind. In no event shall the authors be liable for any damages arising from the use of this software. SPDX-License-Identifier: 0BSD OR MIT-0 DNS-nsdiff-1.85/lib/0000755000175000017500000000000014576605207012532 5ustar fanffanfDNS-nsdiff-1.85/lib/DNS/0000755000175000017500000000000014576605207013156 5ustar fanffanfDNS-nsdiff-1.85/lib/DNS/nsdiff.pm0000644000175000017500000000205714576530741014771 0ustar fanffanf# SPDX-License-Identifier: 0BSD OR MIT-0 package DNS::nsdiff; our $VERSION = "1.85"; =head1 NAME DNS::nsdiff - the nsdiff, nspatch, and nsvi scripts =head1 DESCRIPTION This is a stub module for overview documentation. There are three scripts in the DNS::nsdiff distribution: =over =item B The B program examines the old and new versions of a DNS zone, and outputs the differences as a script for use by BIND's B program. It provides a bridge between static zone files and dynamic updates. =item B The B script is a wrapper around C that checks and reports errors in a manner suitable for running from B. =item B The B script makes it easy to edit a dynamic zone. =back =head1 VERSION This is DNS::nsdiff-1.85 Written by Tony Finch at Cambridge University Information Services. You may do anything with this. It has no warranty. =head1 SEE ALSO nsdiff(1), nspatch(1), nsvi(1), nsupdate(1) =cut 1;