Changeset 881


Ignore:
Timestamp:
01/20/23 16:37:06 (16 months ago)
Author:
Kris Deugau
Message:

/trunk

BIND export, unwinding dev saves, 29 of ~35?

  • Check and create directory path to full zone file pathnames if needed

New(ish) work creeping in:
Clean up, sort, reorder, and otherwise harmonize revzone and domain loops:

  • Cross-copy/merge docucomments for most operational/semantic blocks
  • Reorder checks, tweaks, and cleanups
  • Make sure both loops are checking the same conditions in the same order
  • Trim data retrieval for SOA records, since they have no distance/weight/port by definition, and the type is defined. Update printrec_bind() calls to match.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/DNSDB/ExportBIND.pm

    r880 r881  
    4040## export reverse zones
    4141
    42   my $soasth = $dnsdb->{dbh}->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location ".
    43         "FROM records WHERE rdns_id=? AND type=6");
     42  my $soasth = $dnsdb->{dbh}->prepare("SELECT host,val,ttl,record_id,location FROM records WHERE rdns_id=? AND type=6");
    4443  # record order matters for reverse zones because we need to override larger templates with smaller ones.
    45   my $recsth = $dnsdb->{dbh}->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location,extract(epoch from stamp),expires,stampactive ".
    46         "FROM records WHERE rdns_id=? AND NOT type=6 ".
    47         "ORDER BY masklen(inetlazy(val)) DESC, inetlazy(val)");
     44  my $recsth = $dnsdb->{dbh}->prepare(
     45        "SELECT host,type,val,distance,weight,port,ttl,record_id,location,extract(epoch from stamp),expires,stampactive ".
     46        "FROM records WHERE rdns_id=? AND NOT type=6 ".
     47        "ORDER BY masklen(inetlazy(val)) DESC, inetlazy(val), record_id");
    4848
    4949  # Fetch active zone list
    5050  my $revsth = $dnsdb->{dbh}->prepare("SELECT rdns_id,revnet,status,changed,default_location FROM revzones WHERE status=1 ".
    51         "ORDER BY masklen(revnet),revnet DESC, rdns_id");
     51        "ORDER BY masklen(revnet),revnet DESC, rdns_id");
    5252  # Unflag changed zones, so we can maybe cache the export and not redo everything every time
    5353  my $zonesth = $dnsdb->{dbh}->prepare("UPDATE revzones SET changed='n' WHERE rdns_id=?");
     54
     55  my %recflags;  # need this to be independent for forward vs reverse zones, as they're not merged
     56
    5457  $revsth->execute();
    55 
    56   my %recflags;  # need this to be independent for forward vs reverse zones, as they're not merged
    57 
    5858  while (my ($revid,$revzone,$revstat,$changed,$defloc) = $revsth->fetchrow_array) {
    5959    my $cidr = NetAddr::IP->new($revzone);
     
    6969    # fetch a list of views/locations present in the zone.  we need to publish a file for each one.
    7070    # in the event that no locations are present (~~ $viewlist is empty), /%view collapses to nothing in the zone path
    71 #    my (@loclist) = $dnsdb->{dbh}->selectrow_array("SELECT DISTINCT location FROM records WHERE rdns_id = ?", undef, $revid);
    7271    my $tmplocs = $dnsdb->{dbh}->selectall_arrayref("SELECT DISTINCT location FROM records WHERE rdns_id = ?", undef, $revid);
    7372    my @loclist;
     
    8079    eval {
    8180
     81##fixme:  use tmpfile module for more secure temp files?  want the zone name at least in it anyway, not sure that works...
    8282      my $arpazone = DNSDB::_ZONE($cidr, 'ZONE', 'r', '.').($cidr->{isv6} ? '.ip6.arpa' : '.in-addr.arpa');
    8383      my $zfile = $cidr->network->addr."-".$cidr->masklen;
     
    9696        $zfilepath =~ s,[^\w./-],_,g;
    9797
    98 #        open $zonefiles{$loc}, ">", $zfilepath;
     98        # safety check, may need tweaking for race conditions
     99        my $zpathbase = $zfilepath;
     100        $zpathbase =~ s{/[^/]+$}{};
     101        if (!-e $zpathbase) {
     102          mkdir $zpathbase;
     103        } else {
     104          die "$zpathbase is not a directory\n" unless -d $zpathbase;
     105        }
    99106
    100107        # write fresh records if:
    101         #  - we are not using the cache
     108        #  - the zone contains records which expire in less than 10 minutes or became valid less than 10 minutes ago
     109        # note, no need to multi-bump the serial
     110        if ( ($dnsdb->{dbh}->selectrow_array("SELECT COUNT(*) FROM records WHERE rdns_id = ? AND ".
     111                "stampactive='t' AND @(extract(epoch from stamp-now())) < 600", undef, $revid))[0] ) {
     112          $changed = 1;
     113          $dnsdb->_updateserial(domain_id => $domid);
     114        }
     115#  - we are not using the cache
     116# if ($dnsdb->{usecache}
    102117        #  - force_refresh is set
    103118        #  - the zone has changed
    104         #  - the cache file does not exist
    105         #  - the cache file is empty
    106         if ($dnsdb->{force_refresh} || $changed || !-e $zfilepath || -z $zfilepath) {
    107 #        if (!$dnsdb->{usecache} || $dnsdb->{force_refresh} || $changed || !-e $cachefile || -z $cachefile) {
     119        #  - the zone file does not exist
     120        #  - the zone file is empty
     121        elsif ($dnsdb->{force_refresh} || $changed || !-e $zfilepath || -z $zfilepath) {
    108122#          if ($dnsdb->{usecache}) {
    109123#            open ZONECACHE, ">$tmpcache" or die "Error creating temporary file $tmpcache: $!\n";
     
    112126          open $zonefiles{$loc}, ">", $zfilepath or die "Error creating temporary file $zfilepath: $!\n";
    113127
     128          # Header for human convenience
     129##fixme?  vary arpazone/cidr in header and error message per showrev_arpa, or possibly
     130# new dedicated setting, or possibly interact with with bind_export_fqdn?
    114131          printf {$zonefiles{$loc}} "; %s in view %s exported %s\n", $arpazone, $loc, scalar(localtime)
    115             or die "Error writing header [$cidr, '$loc']: $!\n";
    116 
    117           # need to fetch this separately since the rest of the records all (should) have real IPs in val
     132            or die "Error writing header [$arpazone, '$loc']: $!\n";
     133
     134          # Fetch the SOA separately as we publish it separately for each location with this loop,
     135          # mainly because we want it first in the zone file
    118136          $soasth->execute($revid);
    119           my (@zsoa) = $soasth->fetchrow_array();
     137          my ($soa_host, $soa_val, $soa_ttl, $soa_id, $soa_loc) = $soasth->fetchrow_array;
     138
    120139##fixme: do we even need @loclist passed in?
    121           printrec_bind($dnsdb, \%zonefiles, \@loclist, $zsoa[7], 'y', \%recflags, $cidr,
    122             $zsoa[0], $zsoa[1], $zsoa[2], $zsoa[3], $zsoa[4], $zsoa[5], $zsoa[6], $loc, '');
     140          printrec_bind($dnsdb, \%zonefiles, \@loclist, $soa_id, 'y', \%recflags, $cidr,
     141            $soa_host, 6, $soa_val, 0, 0, 0, $soa_ttl, $loc, '');
     142
    123143        } # if force_refresh etc
    124144
    125145        # tag the zonefile for publication in the view
    126146        push @{$viewzones{$loc}}, $arpazone;
     147
    127148      } # foreach @loclist
    128149
    129150      # now the meat of the records
    130151      $recsth->execute($revid);
    131 
    132152      while (my ($host, $type, $val, $dist, $weight, $port, $ttl, $recid, $loc, $stamp, $expires, $stampactive)
    133153                = $recsth->fetchrow_array) {
    134154        next if $recflags{$recid};
     155
     156        # Spaces are evil.
     157        $val =~ s/^\s+//;
     158        $val =~ s/\s+$//;
     159        if ($typemap{$type} ne 'TXT') {
     160          # Leading or trailng spaces could be legit in TXT records.
     161          $host =~ s/^\s+//;
     162          $host =~ s/\s+$//;
     163        }
    135164
    136165        # Check for out-of-zone data
     
    149178        } # is $val a raw .arpa name?
    150179
    151         # Spaces are evil.
    152         $val =~ s/^\s+//;
    153         $val =~ s/\s+$//;
    154         if ($typemap{$type} ne 'TXT') {
    155           # Leading or trailng spaces could be legit in TXT records.
    156           $host =~ s/^\s+//;
    157           $host =~ s/\s+$//;
    158         }
    159 
    160180        printrec_bind($dnsdb, \%zonefiles, \@loclist, $recid, 'y', \%recflags, $revzone,
    161181                $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp, $expires, $stampactive);
     
    202222## and now the domains
    203223
    204   $soasth = $dnsdb->{dbh}->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location ".
    205         "FROM records WHERE domain_id=? AND type=6");
     224  $soasth = $dnsdb->{dbh}->prepare("SELECT host,val,ttl,record_id,location FROM records WHERE domain_id=? AND type=6");
    206225  # record order needs to match reverse zone ordering for IP values, or A+PTR
    207226  # template records don't cascade/expand correctly to match the reverse zones.
     
    211230  # ordering by nominal parent-child label hierarchy (as actually found live
    212231  # in some AXFRed zone files) would take a lot of chewing on data
    213   $recsth = $dnsdb->{dbh}->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location,extract(epoch from stamp),expires,stampactive ".
    214         "FROM records WHERE domain_id=? AND NOT type=6 ".
    215         "ORDER BY masklen(inetlazy(val)) DESC, inetlazy(val), record_id");
     232  $recsth = $dnsdb->{dbh}->prepare(
     233        "SELECT host,type,val,distance,weight,port,ttl,record_id,location,extract(epoch from stamp),expires,stampactive ".
     234        "FROM records WHERE domain_id=? AND NOT type=6 ".
     235        "ORDER BY masklen(inetlazy(val)) DESC, inetlazy(val), record_id");
    216236#      "FROM records WHERE domain_id=? AND type < 65280");     # Just exclude all types relating to rDNS
    217237
    218238  # Fetch active zone list
    219   my $domsth = $dnsdb->{dbh}->prepare("SELECT domain_id,domain,status,changed FROM domains WHERE status=1 ORDER BY domain_id");
     239  my $domsth = $dnsdb->{dbh}->prepare("SELECT domain_id,domain,status,changed,default_location FROM domains WHERE status=1 ".
     240        "ORDER BY domain_id");
    220241  # Unflag changed zones, so we can maybe cache the export and not redo everything every time
    221242  $zonesth = $dnsdb->{dbh}->prepare("UPDATE domains SET changed='n' WHERE domain_id=?");
    222   $domsth->execute();
    223243
    224244  # Clear %reclfags, since we explicitly want to NOT carry "I've published this
     
    231251#  %recflags = ();
    232252
     253  $domsth->execute();
    233254  while (my ($domid,$domain,$domstat,$changed) = $domsth->fetchrow_array) {
    234255
     
    240261      push @loclist, ($tloc->[0] eq '' ? 'common' : $tloc->[0]);
    241262    }
     263
    242264    my %zonefiles;  # zone file handles
    243265
     
    247269      my $zfile = $domain;  # can probably drop this intermediate
    248270      my $tmpcache = "tmp.$zfile.$$";   # safety net.  don't overwrite a previous known-good file
     271
    249272      foreach my $loc (@loclist) {
    250273        my $zfilepath = $dnsdb->{bind_export_zone_path};
    251274        $zfilepath =~ s/\%view/$loc/;
    252275        $zfilepath =~ s/\%zone/$zfile/;
    253 #        $zfilepath =~ s/\%arpazone/$arpazone/;
    254276
    255277        # Just In Case(TM)
    256278        $zfilepath =~ s,[^\w./-],_,g;
    257279
    258 #      open $zonefiles{$loc}, ">", $zfilepath;
    259 print "open zonefile for '$loc', '$zfilepath'\n";
    260 
     280        # safety check, may need tweaking for race conditions
     281        my $zpathbase = $zfilepath;
     282        $zpathbase =~ s{/[^/]+$}{};
     283        if (!-e $zpathbase) {
     284          mkdir $zpathbase;
     285        } else {
     286          die "$zpathbase is not a directory\n" unless -d $zpathbase;
     287        }
    261288
    262289        # write fresh records if:
     
    275302          $dnsdb->_updateserial(domain_id => $domid);
    276303        }
     304#  - we are not using the cache
     305# if ($dnsdb->{usecache}
     306        #  - force_refresh is set
     307        #  - the zone has changed
     308        #  - the zone file does not exist
     309        #  - the zone file is empty
    277310#        if (!$self->{usecache} || $self->{force_refresh} || $changed || !-e $cachefile || -z $cachefile) {
    278311        if ($dnsdb->{force_refresh} || $changed || !-e $zfilepath || -z $zfilepath) {
    279           open $zonefiles{$loc}, ">", $zfilepath or die "Error creating temporary file $zfilepath: $!\n";
    280 
    281312#          if ($self->{usecache}) {
    282313#            open ZONECACHE, ">$tmpcache" or die "Error creating temporary file $tmpcache: $!\n";
    283314#            $zonefilehandle = *ZONECACHE;
    284315#          }
    285 
    286           # need to fetch this separately so the SOA comes first in the flatfile....
    287           # Just In Case we need/want to reimport from the flatfile later on.
    288           $soasth->execute($domid);
    289           my (@zsoa) = $soasth->fetchrow_array();
    290 
    291           # drop in a header line so we know when things went KABOOM
     316          open $zonefiles{$loc}, ">", $zfilepath or die "Error creating temporary file $zfilepath: $!\n";
     317
     318          # Header for human convenience
    292319          printf {$zonefiles{$loc}} "; %s in view %s exported %s\n", $domain, $loc, scalar(localtime)
    293320                or die "Error writing header [$domain, '$loc']: $!\n";
    294321
    295           printrec_bind($dnsdb, \%zonefiles, \@loclist, $zsoa[7], 'n', \%recflags, $domain,
    296             $zsoa[0], $zsoa[1], $zsoa[2], $zsoa[3], $zsoa[4], $zsoa[5], $zsoa[6], $loc, '');
    297 
    298 #          $self->_printrec_tiny($zonefilehandle, $zsoa[7], 'n',\%recflags, $domain,
    299 #            $zsoa[0],$zsoa[1],$zsoa[2],$zsoa[3],$zsoa[4],$zsoa[5],$zsoa[6],$zsoa[8],'');
     322          # Fetch the SOA separately as we publish it separately for each location with this loop,
     323          # mainly because we want it first in the zone file
     324          $soasth->execute($domid);
     325          my ($soa_host, $soa_val, $soa_ttl, $soa_id, $soa_loc) = $soasth->fetchrow_array;
     326$dnsdb->{dbh}->selectrow_array(
     327            "SELECT host,val,ttl,record_id,location FROM records WHERE domain_id=? AND type=6");
     328
     329##fixme: do we even need @loclist passed in?
     330          printrec_bind($dnsdb, \%zonefiles, \@loclist, $soa_id, 'n', \%recflags, $domain,
     331            $soa_host, 6, $soa_val, 0, 0, 0, $soa_ttl, $loc, '');
    300332
    301333        } # if force_refresh etc
     
    303335        # tag the zonefile for publication in the view
    304336        push @{$viewzones{$loc}}, $domain;
     337
    305338      } # foreach @loclist
    306339
     340      # now the meat of the records
    307341      $recsth->execute($domid);
    308342      while (my ($host,$type,$val,$dist,$weight,$port,$ttl,$recid,$loc,$stamp,$expires,$stampactive) = $recsth->fetchrow_array) {
     
    327361        }
    328362
    329         $recflags{$recid} = 1;
    330 
    331363        printrec_bind($dnsdb, \%zonefiles, \@loclist, $recid, 'n', \%recflags, $domain,
    332364          $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp, $expires, $stampactive);
     365
     366        $recflags{$recid} = 1;
    333367
    334368      } # while ($recsth)
Note: See TracChangeset for help on using the changeset viewer.