1#!/usr/bin/perl 2# $Id: axfr 1815 2020-10-14 21:55:18Z willem $ 3 4use strict; 5use warnings; 6use vars qw($opt_f $opt_q $opt_s $opt_D); 7use File::Basename; 8use Getopt::Std; 9use Net::DNS; 10use Storable; 11 12#------------------------------------------------------------------------------ 13# Read any command-line options and check syntax. 14#------------------------------------------------------------------------------ 15 16getopts("fqsD:"); 17 18die "Usage: ", basename($0), " [ -fqs ] [ -D directory ] [ \@nameserver ] zone\n" 19 unless (@ARGV >= 1) && (@ARGV <= 2); 20 21#------------------------------------------------------------------------------ 22# Get the nameserver (if specified) and set up the zone transfer directory 23# hierarchy. 24#------------------------------------------------------------------------------ 25 26my $nameserver = ($ARGV[0] =~ /^@/) ? shift @ARGV : ""; 27$nameserver =~ s/^@//; 28 29my $zone = shift @ARGV; 30my $basedir = defined $opt_D ? $opt_D : $ENV{"HOME"} . "/.dns-zones"; 31my $zonedir = join("/", reverse(split(/\./, $zone))); 32my $zonefile = $basedir . "/" . $zonedir . "/axfr"; 33 34# Don't worry about the 0777 permissions here - the current umask setting 35# will be applied. 36unless (-d $basedir) { 37 mkdir($basedir, 0777) or die "can't mkdir $basedir: $!\n"; 38} 39 40my $dir = $basedir; 41my $subdir; 42foreach my $subdir (split(m#/#, $zonedir)) { 43 $dir .= "/" . $subdir; 44 unless (-d $dir) { 45 mkdir($dir, 0777) or die "can't mkdir $dir: $!\n"; 46 } 47} 48 49#------------------------------------------------------------------------------ 50# Get the zone. 51#------------------------------------------------------------------------------ 52 53my $res = Net::DNS::Resolver->new; 54$res->nameservers($nameserver) if $nameserver; 55 56my (@zone, $zoneref); 57 58if (-e $zonefile && !defined $opt_f) { 59 $zoneref = retrieve($zonefile) || die "couldn't retrieve zone from $zonefile: $!\n"; 60 61 #---------------------------------------------------------------------- 62 # Check the SOA serial number if desired. 63 #---------------------------------------------------------------------- 64 65 if (defined $opt_s) { 66 my($serial_file, $serial_zone); 67 68 my $rr; 69 foreach my $rr (@$zoneref) { 70 if ($rr->type eq "SOA") { 71 $serial_file = $rr->serial; 72 last; 73 } 74 } 75 die "no SOA in $zonefile\n" unless defined $serial_file; 76 77 my $soa = $res->query($zone, "SOA"); 78 die "couldn't get SOA for $zone: ", $res->errorstring, "\n" 79 unless defined $soa; 80 81 foreach my $rr ($soa->answer) { 82 if ($rr->type eq "SOA") { 83 $serial_zone = $rr->serial; 84 last; 85 } 86 } 87 88 if ($serial_zone != $serial_file) { 89 $opt_f = 1; 90 } 91 } 92} else { 93 $opt_f = 1; 94} 95 96if (defined $opt_f) { 97 @zone = $res->axfr($zone); 98 die "couldn't transfer zone: ", $res->errorstring, "\n" unless @zone; 99 store \@zone, $zonefile or die "couldn't store zone to $zonefile: $!\n"; 100 $zoneref = \@zone; 101} 102 103#------------------------------------------------------------------------------ 104# Print the records in the zone. 105#------------------------------------------------------------------------------ 106 107unless ($opt_q) { 108 $_->print for @$zoneref 109} 110 111__END__ 112 113=head1 NAME 114 115axfr - Perform a DNS zone transfer 116 117=head1 SYNOPSIS 118 119B<axfr> S<[ B<-fqs> ]> S<[ B<-D> I<directory> ]> S<[ B<@>I<nameserver> ]> 120I<zone> 121 122=head1 DESCRIPTION 123 124B<axfr> performs a DNS zone transfer, prints each record to the standard 125output, and stores the zone to a file. If the zone has already been 126stored in a file, B<axfr> will read the file instead of performing a 127zone transfer. 128 129Zones will be stored in a directory hierarchy. For example, the 130zone transfer for foo.bar.com will be stored in the file 131$HOME/.dns-zones/com/bar/foo/axfr. The directory can be changed 132with the B<-D> option. 133 134This programs requires that the Storable module be installed. 135 136=head1 OPTIONS 137 138=over 4 139 140=item B<-f> 141 142Force a zone transfer, even if the zone has already been stored 143in a file. 144 145=item B<-q> 146 147Be quiet -- don't print the records from the zone. 148 149=item B<-s> 150 151Perform a zone transfer if the SOA serial number on the nameserver 152is different than the serial number in the zone file. 153 154=item B<-D> I<directory> 155 156Store zone files under I<directory> instead of the default directory 157(see L<"FILES">). 158 159=item B<@>I<nameserver> 160 161Query I<nameserver> instead of the default nameserver. 162 163=back 164 165=head1 FILES 166 167=over 4 168 169=item B<$HOME/.dns-zones> 170 171Default directory for storing zone files. 172 173=back 174 175=head1 AUTHOR 176 177Michael Fuhr <mike@fuhr.org> 178 179=head1 SEE ALSO 180 181L<perl(1)>, L<check_soa>, L<check_zone>, L<mresolv>, L<mx>, L<perldig>, 182L<Net::DNS>, L<Storable> 183 184=cut 185