Ignore:
Timestamp:
02/10/26 13:30:58 (19 hours ago)
Author:
Kris Deugau
Message:

/branches/stable

Start merging changes from /trunk forward based on changes actually applied
in production

Location:
branches/stable
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • branches/stable

  • branches/stable/DNSDB.pm

    r756 r1032  
    33##
    44# $Id$
    5 # Copyright 2008-2016 Kris Deugau <kdeugau@deepnet.cx>
     5# Copyright 2008-2017 Kris Deugau <kdeugau@deepnet.cx>
    66#
    77#    This program is free software: you can redistribute it and/or modify
     
    983983  ${$args{weight}} =~ s/\s*//g;
    984984  ${$args{port}} =~ s/\s*//g;
    985   return ('FAIL',"Distance, port and weight are required, and must be numeric")
    986         unless ${$args{weight}} =~ /^\d+$/ && ${$args{port}} =~ /^\d+$/;
     985  my @ferr;
     986  push @ferr, "distance" unless ${$args{dist}} =~ /^\d+$/;
     987  push @ferr, "weight" unless ${$args{weight}} =~ /^\d+$/;
     988  push @ferr, "port" unless ${$args{port}} =~ /^\d+$/;
     989  return ('FAIL',"Distance, port and weight are required, and must be numeric (check ".join(",", @ferr).")")
     990        unless ${$args{dist}} =~ /^\d+$/ && ${$args{weight}} =~ /^\d+$/ && ${$args{port}} =~ /^\d+$/;
    987991
    988992  ${$args{fields}} = "distance,weight,port,";
     
    13691373  }
    13701374  return ('OK','OK');
    1371 }
     1375} # done delegation record
     1376
     1377# ALIAS record
     1378# A specialized variant of the CNAME, which retrieves the A record list on each
     1379# export and publishes the A records instead.  Primarily for "root CNAME" or "apex
     1380# alias" records.  See https://secure.deepnet.cx/trac/dnsadmin/ticket/55.
     1381# Not allowed in reverse zones because this is already a hack, and reverse zones
     1382# don't get pointed to CNAMEed CDNs the way domains do.
     1383sub _validate_65300 {
     1384  my $self = shift;
     1385  my $dbh = $self->{dbh};
     1386
     1387  my %args = @_;
     1388
     1389  return ('FAIL',"ALIAS records are not permitted in reverse zones") if $args{revrec} eq 'y';
     1390
     1391  # Make sure target is a well-formed hostname
     1392  return ('FAIL', $errstr) if ! _check_hostname_form(${$args{val}}, ${$args{rectype}}, $args{defrec}, $args{revrec});
     1393
     1394  # Coerce all hostnames to end in ".DOMAIN" for group/default records,
     1395  # or the intended parent domain for live records.
     1396  my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : $self->domainName($args{id}));
     1397  ${$args{host}} =~ s/\.*$/\.$pname/ if (${$args{host}} ne '@' && ${$args{host}} !~ /$pname$/i);
     1398
     1399  # Only do the cache thing on live/active records
     1400  return ('OK','OK') unless $args{defrec} eq 'n';
     1401
     1402  # now we check/update the cached target address info
     1403  my ($iplist) = $self->{dbh}->selectrow_array("SELECT auxdata FROM records WHERE record_id = ?", undef, $args{recid});
     1404  my $warnmsg;
     1405  $iplist = '' if !$iplist;
     1406
     1407  # shared target-name-to-IP converter
     1408  my $liveips = $self->_grab_65300($args{recid}, ${$args{val}});
     1409  $liveips = '' if !$liveips;
     1410
     1411  # check to see if there was an OOOOPS checking for updated A records on the target.  also make sure we have something cached.
     1412  if (!$liveips) {
     1413    if (!$iplist) {
     1414      # not fatal since we do the lookup on export as well
     1415      $warnmsg = "No cached data and no live DNS data for ALIAS target ${$args{val}};  record may be SKIPPED on export!";
     1416    }
     1417  }
     1418
     1419  # munge the insert/update fieldlist and data array
     1420  # note we always force this;  if the target has changed the cached data is almost certainly invalid anyway
     1421  if ($liveips && ($iplist ne $liveips)) {
     1422    ${$args{fields}} .= "auxdata,";
     1423    push @{$args{vallist}}, $liveips;
     1424  }
     1425
     1426  return ('WARN', join("\n", $errstr, $warnmsg) ) if $warnmsg;
     1427 
     1428  return ('OK','OK');
     1429} # done ALIAS record
     1430
     1431# this segment used multiple places to update ALIAS target details
     1432sub _grab_65300 {
     1433  my $self = shift;
     1434  my $dbh = $self->{dbh};
     1435
     1436  my $recid = shift;
     1437  my $target = shift;
     1438
     1439  my $res = Net::DNS::Resolver->new;
     1440  $res->tcp_timeout(2);
     1441  $res->udp_timeout(2);
     1442  my $reply = $res->query($target);
     1443
     1444  my $liveips = '';
     1445  if ($reply) {
     1446    # default to a one-hour TTL, which should be variously modified down the chain.  Arguably this could
     1447    # default even lower, since "The Cloud" often uses sub-1-minute TTLs on the final A records.
     1448    my $minttl = 3600;
     1449    my @newlist;
     1450    foreach my $rr ($reply->answer) {  #@alist) {
     1451      next unless $rr->type eq "A";
     1452      push @newlist, $rr->address;
     1453      $minttl = $rr->ttl if $rr->ttl < $minttl;
     1454    }
     1455    # safety limit.  could arguably take this lower, or for extra
     1456    # complexity, reference off the zone SOA minTTL
     1457    $minttl = 60 if $minttl < 60;
     1458    # we don't need this to be perfectly correct IP address order, just consistent.
     1459    $liveips = "$minttl:".join(':', sort(@newlist)) if @newlist;
     1460#fixme:  should it be a formal error case if there are no A records returned?
     1461  } else {
     1462    $errstr = "Lookup failure retrieving ALIAS IP list: ".$res->errorstring;
     1463  }
     1464
     1465  return $liveips;
     1466} # _grab_65300()
     1467
    13721468
    13731469# Subs not specific to a particular record type
     
    44164512
    44174513  # do simple validation first
     4514  $ttl = '' if !$ttl;
     4515  $ttl =~ s/\s*//g;
    44184516  return ('FAIL', "TTL must be numeric") unless $ttl =~ /^-?\d+$/;
    44194517
     
    45664664
    45674665  # do simple validation first
     4666  $ttl = '' if !$ttl;
     4667  $ttl =~ s/\s*//g;
    45684668  return ('FAIL', "TTL must be numeric") unless $ttl =~ /^-?\d+$/;
    45694669
     
    59246024  # Error check - does the cache dir exist, if we're using one?
    59256025  if ($self->{usecache}) {
    5926     die "Cache directory does not exist\n" if !-e $self->{exportcache};
     6026    die "Cache directory $self->{exportcache} does not exist\n" if !-e $self->{exportcache};
    59276027    die "$self->{exportcache} is not a directory\n" if !-d $self->{exportcache};
    59286028    die "$self->{exportcache} must be both readable and writable\n"
     
    61386238      #  - the cache file does not exist
    61396239      #  - the cache file is empty
     6240      #  - the zone contains ALIAS pseudorecords, which need to cascade changes from the upstream CNAME farm at every opportunity
     6241      if ( ($dbh->selectrow_array("SELECT count(*) FROM records WHERE domain_id = ? AND type=65300", undef, $domid))[0] ) {
     6242        $changed = 1;  # abuse this flag for zones with ALIAS records
     6243      }
    61406244      if (!$self->{usecache} || $self->{force_refresh} || $changed || !-e $cachefile || -z $cachefile) {
    61416245        if ($self->{usecache}) {
     
    63986502    print $datafile "Z$zone:$primary:$email"."::$refresh:$retry:$expire:$min_ttl:$ttl:$stamp:$loc\n"
    63996503      or die $!;
    6400 
    6401   } elsif ($typemap{$type} eq 'A') {
    6402 
     6504  } # SOA
     6505
     6506  elsif ($typemap{$type} eq 'A') {
    64036507    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    64046508    print $datafile "+$host:$val:$ttl:$stamp:$loc\n" or die $!;
    6405 
    6406   } elsif ($typemap{$type} eq 'NS') {
    6407 
     6509  } # A
     6510
     6511  elsif ($typemap{$type} eq 'NS') {
    64086512    if ($revrec eq 'y') {
    64096513      $val = NetAddr::IP->new($val);
     
    64316535      print $datafile "\&$host"."::$val:$ttl:$stamp:$loc\n" or die $!;
    64326536    }
    6433 
    6434   } elsif ($typemap{$type} eq 'AAAA') {
    6435 
     6537  } # NS
     6538
     6539  elsif ($typemap{$type} eq 'AAAA') {
    64366540    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    64376541    my $altgrp = 0;
     
    64556559    }
    64566560    print $datafile "$prefix:$ttl:$stamp:$loc\n" or die $!;
    6457 
    6458   } elsif ($typemap{$type} eq 'MX') {
    6459 
     6561  } # AAAA
     6562
     6563  elsif ($typemap{$type} eq 'MX') {
    64606564    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    64616565    print $datafile "\@$host"."::$val:$dist:$ttl:$stamp:$loc\n" or die $!;
    6462 
    6463   } elsif ($typemap{$type} eq 'TXT') {
    6464 
     6566  } # MX
     6567
     6568  elsif ($typemap{$type} eq 'TXT') {
    64656569    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    64666570# le sigh.  Some idiot DNS implementations don't seem to like tinydns autosplitting
     
    65066610#:3600
    65076611
    6508   } elsif ($typemap{$type} eq 'CNAME') {
    6509 
     6612  } # TXT
     6613
     6614  elsif ($typemap{$type} eq 'CNAME') {
    65106615    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    65116616    print $datafile "C$host:$val:$ttl:$stamp:$loc\n" or die $!;
    6512 
    6513   } elsif ($typemap{$type} eq 'SRV') {
    6514 
     6617  } # CNAME
     6618
     6619  elsif ($typemap{$type} eq 'SRV') {
    65156620    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    65166621
     
    65256630    }
    65266631    print $datafile "$prefix\\000:$ttl:$stamp:$loc\n" or die $!;
    6527 
    6528   } elsif ($typemap{$type} eq 'RP') {
    6529 
     6632  } # SRV
     6633
     6634  elsif ($typemap{$type} eq 'RP') {
    65306635    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    65316636    # RP consists of two mostly free-form strings.
     
    65426647    }
    65436648    print $datafile "$prefix\\000:$ttl:$stamp:$loc\n" or die $!;
    6544 
    6545   } elsif ($typemap{$type} eq 'PTR') {
    6546 
     6649  } # RP
     6650
     6651  elsif ($typemap{$type} eq 'PTR') {
    65476652    $$recflags{$val}++;
    65486653    if ($revrec eq 'y') {
     
    65736678      print $datafile "^$host:$val:$ttl:$stamp:$loc\n" or die $!;
    65746679    }
    6575 
    6576   } elsif ($type == 65280) { # A+PTR
    6577 
     6680  } # PTR
     6681
     6682  elsif ($type == 65280) { # A+PTR
    65786683    $$recflags{$val}++;
    65796684    print $datafile "=$host:$val:$ttl:$stamp:$loc\n" or die $!;
    6580 
    6581   } elsif ($type == 65281) { # AAAA+PTR
    6582 
     6685  } # A+PTR
     6686
     6687  elsif ($type == 65281) { # AAAA+PTR
    65836688    $$recflags{$val}++;
    65846689    # treat these as two separate records.  since tinydns doesn't have
     
    65936698##fixme: add a config flag to indicate use of the patch from http://www.fefe.de/dns/
    65946699# type 6 is for AAAA+PTR, type 3 is for AAAA
    6595 
    6596   } elsif ($type == 65282) { # PTR template
    6597 
     6700  } # AAAA+PTR
     6701
     6702  elsif ($type == 65282) { # PTR template
    65986703    # only useful for v4 with standard DNS software, since this expands all
    65996704    # IPs in $zone (or possibly $val?) with autogenerated records
     
    66086713      $self->__publish_subnet($val, $recflags, $host, $datafile, $ttl, $stamp, $loc, $zone, 1);
    66096714    }
    6610 
    6611   } elsif ($type == 65283) { # A+PTR template
    6612 
     6715  } # PTR template
     6716
     6717  elsif ($type == 65283) { # A+PTR template
    66136718    $val = NetAddr::IP->new($val);
    66146719    # Just In Case.  An A+PTR should be impossible to add to a v6 revzone via API.
     
    66226727      $self->__publish_subnet($val, $recflags, $host, $datafile, $ttl, $stamp, $loc, $zone, 0);
    66236728    }
    6624 
    6625   } elsif ($type == 65284) { # AAAA+PTR template
     6729  } # A+PTR template
     6730
     6731  elsif ($type == 65284) { # AAAA+PTR template
    66266732    # Stub for completeness.  Could be exported to DNS software that supports
    66276733    # some degree of internal automagic in generic-record-creation
    66286734    # (eg http://search.cpan.org/dist/AllKnowingDNS/ )
    6629 
    6630   } elsif ($type == 65285) { # Delegation
     6735  } # AAAA+PTR template
     6736
     6737  elsif ($type == 65285) { # Delegation
    66316738    # This is intended for reverse zones, but may prove useful in forward zones.
    66326739
     
    66536760      }
    66546761    }
     6762  } # Delegation
     6763
     6764  elsif ($type == 65300) { # ALIAS
     6765    # Implemented as a unique record in parallel with many other
     6766    # management tools, for clarity VS formal behviour around CNAME
     6767    # Mainly for "root CNAME" or "apex alias";  limited value for any
     6768    # other use case since CNAME can generally be used elsewhere.
     6769
     6770    # .arpa zones don't need this hack.  shouldn't be allowed into
     6771    # the DB in the first place, but Just In Case...
     6772    return if $revrec eq 'y';
     6773
     6774    my ($iplist) = $self->{dbh}->selectrow_array("SELECT auxdata FROM records WHERE record_id = ?", undef, $recid);
     6775    $iplist = '' if !$iplist;
     6776
     6777    # shared target-name-to-IP converter
     6778    my $liveips = $self->_grab_65300($recid, $val);
     6779    # only update the cache if the live lookup actually returned data
     6780    if ($liveips && ($iplist ne $liveips)) {
     6781      $self->{dbh}->do("UPDATE records SET auxdata = ? WHERE record_id = ?", undef, $liveips, $recid);
     6782      $iplist = $liveips;
     6783    }
     6784
     6785    # slice the TTL we'll actually publish off the front
     6786    my @asubs = split ':', $iplist;
     6787    my $attl = shift @asubs;
     6788
     6789    # output a plain old A record for each IP the target name really points to.
     6790    # in the event that, for whatever reason, no A records are available for $val, nothing will be output.
     6791    foreach my $subip (@asubs) {
     6792      print $datafile "+$host:$subip:$attl:$stamp:$loc\n" or die $!;
     6793    }
     6794  } # ALIAS
    66556795
    66566796##
     
    66586798##
    66596799
    6660   } elsif ($type == 44) { # SSHFP
    6661 
     6800  elsif ($type == 44) { # SSHFP
    66626801    ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
    66636802
     
    66716810    print $datafile "$rec:$ttl:$stamp:$loc\n" or die $!;
    66726811
    6673   } else {
     6812  } # SSHFP
     6813
     6814  else {
    66746815    # raw record.  we don't know what's in here, so we ASS-U-ME the user has
    66756816    # put it in correctly, since either the user is messing directly with the
     
    66826823    #print $datafile ":$host:$type:$val:$ttl:$stamp:$loc\n";
    66836824
    6684   } # record type if-else
     6825  } # "other"
    66856826
    66866827} # end _printrec_tiny()
Note: See TracChangeset for help on using the changeset viewer.