1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
|
#!/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 (<DATA>) {
print if m{^=head1 VERSION} ... m{^=head1 }
and not m{^=head1 };
}
exit;
}
sub usage {
print STDERR <<EOF;
usage: nsvi [options] <zone>
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<keyfile>] [B<-y> [I<hmac>:]I<name>:I<key>]
[B<-S> I<mode>|I<num>] [B<-s> I<server>] <I<zone>>
=head1 DESCRIPTION
The B<nsvi> program makes an AXFR request for the zone, runs your
editor so you can make whatever changes you require, then it runs
B<nsdiff> | B<nsupdate> 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<nsvi> options are passed to B<nsdiff> and some to B<nsupdate>.
=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<U> or B<Y>), edit again (press
B<E> or B<R>), or quit (press another key).
=item B<-01cCdD>
=item B<-S> B<mode>|I<num>
These options are passed to B<nsdiff>.
For details see the nsdiff manual.
=item B<-s> I<server>[#I<port>]
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<localhost>, and B<nsvi> will use B<nsupdate> B<-l> to update
the zone.
=item B<-g>
Passed to B<nsupdate> to use GSS-TSIG for UPDATE.
=item B<-k> I<keyfile>
TSIG key file, passed to B<dig>, B<nsdiff>, and B<nsupdate>.
=item B<-y> [I<hmac>:]I<name>:I<key>
Literal TSIG key, passed to B<dig>, B<nsdiff>, and B<nsupdate>.
=back
=head1 ENVIRONMENT
=over
=item B<TMPDIR>
Location for temporary files.
=item B<VISUAL>
=item B<EDITOR>
Which editor to use. C<$VISUAL> is used if it is set,
otherwise C<$EDITOR>, otherwise B<vi>.
=back
=head1 VERSION
This is nsvi-1.85 <https://dotat.at/prog/nsdiff/>
Written by Tony Finch <fanf2@cam.ac.uk> <dot@dotat.at>
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
|