Changeset 1054 for branches/stable/DNSDB.pm
- Timestamp:
- 03/13/26 14:00:58 (27 hours ago)
- Location:
- branches/stable
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/stable
- Property svn:mergeinfo changed
/branches/cname-collision (added) merged: 936-942,949-956,958-960,962-964,967-970,972-984,986-1001,1003-1006,1008-1031,1038-1046 /trunk merged: 1051
- Property svn:mergeinfo changed
-
branches/stable/DNSDB.pm
r1049 r1054 33 33 use Fcntl qw(:flock); 34 34 use Time::TAI64 qw(:tai64); 35 use Date::Parse; 35 36 36 37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); … … 213 214 force_refresh => 1, 214 215 lowercase => 0, # mangle as little as possible by default 216 # tinker with timestamps if adding or updating a record would 217 # cause overlapping CNAME and other in some way 218 coerce_cname_timestamp => 'none', 215 219 # show IPs and CIDR blocks as-is for reverse zones. valid values are 216 220 # 'none' (default, show natural IP or CIDR) … … 275 279 $self->{showrev_arpa} = 'none'; 276 280 } 281 if (!grep /$self->{coerce_cname_timestamp}/, ('none','adjust','full')) { 282 warn "Bad coerce_cname_timestamp setting $self->{coerce_cname_timestamp}, using default\n"; 283 $self->{coerce_cname_timestamp} = 'none'; 284 } 277 285 278 286 # Try to connect to the DB, and initialize a number of handy globals. … … 561 569 ## Record validation subs. 562 570 ## 571 572 # Check for name collisions relating to CNAMEs. Needs to be called from all other validators. 573 sub _cname_collision { 574 my $self = shift; 575 my $dbh = $self->{dbh}; 576 577 my %args = @_; 578 579 my $hcheck = ($args{revrec} eq 'y' ? ${$args{val}} : ${$args{host}}); 580 my $hfield = ($args{revrec} eq 'y' ? 'val' : 'host'); 581 582 # $hcheck should be normalized by the time this sub is called. Convert to the formal .arpa name for error reporting in reverse zones. 583 my $arpaname = ''; 584 if ($args{revrec} eq 'y') { 585 $arpaname = NetAddr::IP->new($hcheck); 586 ##fixme: more voodoo if global and/or per-user ARPA display mode flag set this way or that 587 $arpaname = _ZONE($arpaname, 'ZONE', 'r', '.').($arpaname->{isv6} ? '.ip6.arpa' : '.in-addr.arpa'); 588 } 589 590 # The record type comparison is the only difference between two passes through this chunk of code. 591 # CNAME records require both passes, where other records only need the second one. Downside is 592 # that returning error messages needs to check the loop variable on top of whatever else it references. 593 foreach my $tcompare ('<>', '=') { 594 next if $tcompare eq '<>' && ${$args{rectype}} != 5; 595 596 # Merging these sets of SQL statements is far too messy and doesn't reasonably 597 # allow for more fine-grained error/warning messages to be returned 598 599 # First lookup fails out collisions with records without timestamps or default records (which can not have timestamps) 600 my $sql = "SELECT count(*) FROM "._rectable($args{defrec}, $args{revrec}). 601 " WHERE "._recparent($args{defrec}, $args{revrec})." = ? AND type $tcompare 5 AND $hfield = ?"; 602 my @lookupargs = ($args{id}, $hcheck); 603 $sql .= " AND stampactive = 'f'" if $args{defrec} eq 'n'; 604 if ($args{update}) { 605 $sql .= " AND record_id <> ?"; 606 push @lookupargs, $args{update}; 607 } 608 my @t = $dbh->selectrow_array($sql, undef, @lookupargs); 609 if ($t[0] > 0) { 610 if ($tcompare eq '<>') { 611 return ('FAIL', "One or more non-CNAME records already exist for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 612 ". CNAME records cannot use the same name as other records."); 613 } else { 614 return ('FAIL', "There is already a CNAME present for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 615 ". Only one CNAME may be present for a given name."); 616 } 617 } 618 619 # By this point, all failure cases for default records have been checked. 620 # Default records cannot carry timestamps, so cannot have timestamp-based collisions 621 return ('OK','OK') if $args{defrec} ne 'n'; 622 623 # Second lookup fails out various timestamp-exists collision cases when adding/updating with a timestamp 624 $sql = "SELECT count(*) FROM "._rectable($args{defrec}, $args{revrec}). 625 " WHERE "._recparent($args{defrec}, $args{revrec})." = ? AND type $tcompare 5 AND $hfield = ?". 626 " AND stampactive = 't'"; 627 @lookupargs = ($args{id}, $hcheck); 628 if (${$args{stamp}} && ${$args{expires}}) { 629 $sql .= " AND expires = ?"; 630 push @lookupargs, ${$args{expires}}; 631 if ($self->{coerce_cname_timestamp} eq 'none') { 632 # no coercion means new valid-after < existing expires or new expires > existing valid-after will fail 633 $sql .= " AND ". (${$args{expires}} eq 'f' ? "expires = 't' AND stamp <= ?)" : "expires = 'f' AND stamp >= ?"); 634 push @lookupargs, ${$args{stamp}}; 635 } 636 } 637 if ($args{update}) { 638 $sql .= " AND record_id <> ?"; 639 push @lookupargs, $args{update}; 640 } 641 @t = $dbh->selectrow_array($sql, undef, @lookupargs); 642 if ($t[0] > 0) { 643 if ($tcompare eq '<>') { 644 return ('FAIL', "One or more non-CNAME records with timestamps already exist for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 645 ". CNAME records must expire before or become valid after any records with the same name."); 646 } else { 647 return ('FAIL', "There is already a CNAME with a timestamp present for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 648 ". Records with a matching name must expire before or become valid after this CNAME."); 649 } 650 } 651 652 # Third check starts retrieving actual timestamps to see if we need to, 653 # and then if we can, coerce the new/updated record's timestamp to match 654 $sql = "SELECT extract(epoch from stamp),expires,stamp < now() FROM "._rectable($args{defrec}, $args{revrec}). 655 " WHERE "._recparent($args{defrec}, $args{revrec})." = ? AND type $tcompare 5 AND $hfield = ?". 656 " AND stampactive = 't'"; 657 @lookupargs = ($args{id}, $hcheck); 658 if ($args{update}) { 659 $sql .= " AND record_id <> ?"; 660 push @lookupargs, $args{update}; 661 } 662 if (${$args{stamp}}) { 663 $sql .= " ORDER BY stamp ".(${$args{expires}} eq 'f' ? 'ASC' : 'DESC' )." LIMIT 1"; 664 } else { 665 $sql .= " ORDER BY stamp LIMIT 1"; 666 } 667 @t = $dbh->selectrow_array($sql, undef, @lookupargs); 668 if (@t) { 669 # caller requested an expiry time 670 my $reqstamp = str2time(${$args{stamp}}); 671 if (${$args{expires}} eq 'f') { 672 if ($reqstamp > $t[0]) { 673 # do nothing, new record goes valid after the expiring record we found 674 } else { 675 if ($self->{coerce_cname_timestamp} eq 'adjust') { 676 # coerce the valid-after timestamp 677 ${$args{stamp}} = strftime('%Y-%m-%d %H:%M:%S', localtime($t[0])); 678 return ('WARN', $typemap{${$args{rectype}}}." ".($args{update} ? 'updated' : 'added'). 679 " with modified valid-after time; conflicting expiring record found"); 680 } else { 681 # New valid-after overlaps existing expiry, and not configured to adjust it 682 my $fill = ($tcompare eq '<>' ? ' CNAME, another record' : $typemap{${$args{rectype}}}.', a CNAME'); 683 return ('FAIL', "Cannot ".($args{update} ? 'update' : 'add').$fill. 684 " with a valid-after time already exists for this name"); 685 } 686 } # else ($reqstamp < $t[0]) 687 } else { 688 if ($reqstamp < $t[0]) { 689 # do nothing, new record will expire before the one we found 690 } else { 691 if ($self->{coerce_cname_timestamp} eq 'adjust') { 692 if ($t[2] == 1) { 693 # found a valid-after, but it's in the past, so adding an expiring record to match doesn't make 694 # sense since it's effectively expired. 695 ##fixme: should probably remove this case once we get around to stripping valid-after timestamps once exported as active 696 return ('FAIL', "Cannot ".($args{update} ? 'update' : 'add')." ".$typemap{${$args{rectype}}}. 697 ", an existing valid-after record is already active for this name"); 698 } else { 699 # coerce the expiry timestamp 700 ${$args{stamp}} = strftime('%Y-%m-%d %H:%M:%S', localtime($t[0])); 701 return ('WARN', $typemap{${$args{rectype}}}." ".($args{update} ? 'updated' : 'added'). 702 " with modified expiry time; conflicting valid-after record found"); 703 } 704 } else { 705 return ('FAIL', "Cannot ".($args{update} ? 'update' : 'add')." ".$typemap{${$args{rectype}}}. 706 ", another record with an overlapping valid-after timestamp already exists for this name"); 707 } 708 } 709 } # args{expires} ne 'f' 710 } # if @t 711 712 } # each $tcompare 713 714 return ('OK', 'OK'); 715 } # _cname_collision() 563 716 564 717 ## All of these subs take substantially the same arguments: … … 758 911 return ('FAIL', "The bare zone name may not be a CNAME") if ${$args{host}} eq $pname || ${$args{host}} =~ /^\@/; 759 912 760 ##enhance: Look up the passed value to see if it exists. Ooo, fancy.761 913 return ('FAIL', $errstr) if ! _check_hostname_form(${$args{val}}, ${$args{rectype}}, $args{defrec}, $args{revrec}); 914 762 915 } # $zname !~ .rpz 763 916 } # revzone eq 'n' … … 1616 1769 1617 1770 return ('WARN', join("\n", $errstr, $warnmsg) ) if $warnmsg; 1618 1771 1619 1772 return ('OK','OK'); 1620 1773 } # done ALIAS record … … 2156 2309 $cfg->{lowercase} = $1 if /^lowercase\s*=\s*([a-z01]+)/i; 2157 2310 $cfg->{showrev_arpa} = $1 if /^showrev_arpa\s*=\s*([a-z]+)/i; 2311 $cfg->{coerce_cname_timestamp} = $1 if /^coerce_cname_timestamp\s*=\s*([a-z]+)/i; 2158 2312 $cfg->{template_skip_0} = $1 if /^template_skip_0\s*=\s*([a-z01]+)/i; 2159 2313 $cfg->{template_skip_255} = $1 if /^template_skip_255\s*=\s*([a-z01]+)/i; … … 4694 4848 4695 4849 my $expires = shift || ''; 4696 $expires = 1 if $expires eq 'until'; # Turn some special values into the appropriate booleans. 4697 $expires = 0 if $expires eq 'after'; 4850 $expires = 't' if $expires eq 'until'; # Turn some special values into the appropriate booleans. 4851 $expires = 'f' if $expires eq 'after'; 4852 $expires = 't' if $expires eq '1'; 4853 $expires = 'f' if $expires eq '0'; 4698 4854 my $stamp = shift; 4699 4855 $stamp = '' if !$stamp; # Timestamp should be a string at this point. … … 4705 4861 return ('FAIL', "expires must be 1, 't', or 'until', or 0, 'f', or 'after'") 4706 4862 if ($stamp && !defined($expires)) 4707 || ($stamp && $expires ne ' 0' && $expires ne '1' && $expires ne 't' && $expires ne 'f');4863 || ($stamp && $expires ne 't' && $expires ne 'f'); 4708 4864 4709 4865 # Spaces are evil. … … 4745 4901 host => $host, rectype => $rectype, val => $val, addr => $addr, 4746 4902 dist => \$dist, port => \$port, weight => \$weight, 4903 stamp => \$stamp, expires => \$expires, 4747 4904 fields => \$fields, vallist => \@vallist); 4748 4905 4749 4906 return ($retcode,$retmsg) if $retcode eq 'FAIL'; 4907 4908 # Check for CNAME collisions. 4909 ##fixme: should trim the list of arguments, not all of these should be required for CNAME collision checking 4910 my ($ccode,$cmsg) = _cname_collision($self, defrec => $defrec, revrec => $revrec, id => $id, 4911 host => $host, rectype => $rectype, val => $val, addr => $addr, 4912 dist => \$dist, port => \$port, weight => \$weight, 4913 stamp => \$stamp, expires => \$expires, 4914 fields => \$fields, vallist => \@vallist); 4915 4916 return ($ccode,$cmsg) if $ccode eq 'FAIL'; 4917 4918 # If both the validator and CNAME collision check return warnings, glue them together 4919 if ($ccode eq 'WARN') { 4920 if ($retcode eq 'WARN') { 4921 $retmsg .= "<br>\n$cmsg"; 4922 } else { 4923 $retmsg = $cmsg; 4924 } 4925 $retcode = 'WARN'; 4926 } 4750 4927 4751 4928 # Minor cleanup of invalid DNS labels … … 4806 4983 $logdata{entry} .= "', TTL $ttl"; 4807 4984 $logdata{entry} .= ", location ".$self->getLoc($location)->{description} if $location; 4808 $logdata{entry} .= ($expires ? ', expires at ' : ', valid after ').$stamp if $stamp;4985 $logdata{entry} .= ($expires eq 't' ? ', expires at ' : ', valid after ').$stamp if $stamp; 4809 4986 4810 4987 # Allow transactions, and raise an exception on errors so we can catch it later. … … 4866 5043 4867 5044 my $expires = shift || ''; 4868 $expires = 1 if $expires eq 'until'; # Turn some special values into the appropriate booleans. 4869 $expires = 0 if $expires eq 'after'; 5045 $expires = 't' if $expires eq 'until'; # Turn some special values into the appropriate booleans. 5046 $expires = 'f' if $expires eq 'after'; 5047 $expires = 't' if $expires eq '1'; 5048 $expires = 'f' if $expires eq '0'; 4870 5049 my $stamp = shift; 4871 5050 $stamp = '' if !$stamp; # Timestamp should be a string at this point. … … 4876 5055 return ('FAIL', "expires must be 1, 't', or 'until', or 0, 'f', or 'after'") 4877 5056 if ($stamp && !defined($expires)) 4878 || ($stamp && $expires ne ' 0' && $expires ne '1' && $expires ne 't' && $expires ne 'f');5057 || ($stamp && $expires ne 't' && $expires ne 'f'); 4879 5058 4880 5059 # Spaces are evil. … … 4922 5101 host => $host, rectype => $rectype, val => $val, addr => $addr, 4923 5102 dist => \$dist, port => \$port, weight => \$weight, 5103 stamp => \$stamp, expires => \$expires, 4924 5104 fields => \$fields, vallist => \@vallist, 4925 5105 update => $id); 4926 5106 4927 5107 return ($retcode,$retmsg) if $retcode eq 'FAIL'; 5108 5109 # Check for CNAME collisions. 5110 ##fixme: should trim the list of arguments, not all of these should be required for CNAME collision checking 5111 my ($ccode,$cmsg) = _cname_collision($self, defrec => $defrec, revrec => $revrec, 5112 id => ($defrec eq 'y' ? $oldrec->{group_id} : ($revrec eq 'n' ? $oldrec->{domain_id} : $oldrec->{rdns_id})), 5113 host => $host, rectype => $rectype, val => $val, addr => $addr, 5114 dist => \$dist, port => \$port, weight => \$weight, 5115 stamp => \$stamp, expires => \$expires, 5116 fields => \$fields, vallist => \@vallist, 5117 update => $id); 5118 5119 return ($ccode,$cmsg) if $ccode eq 'FAIL'; 5120 5121 # If both the validator and CNAME collision check return warnings, glue them together 5122 if ($ccode eq 'WARN') { 5123 if ($retcode eq 'WARN') { 5124 $retmsg .= "<br>\n$cmsg"; 5125 } else { 5126 $retmsg = $cmsg; 5127 } 5128 $retcode = 'WARN'; 5129 } 4928 5130 4929 5131 # Minor cleanup of invalid DNS labels … … 5024 5226 $logdata{entry} .= "', TTL $ttl"; 5025 5227 $logdata{entry} .= ", location ".$self->getLoc($location)->{description} if $location; 5026 $logdata{entry} .= ($expires ? ', expires at ' : ', valid after ').$stamp if $stamp;5228 $logdata{entry} .= ($expires eq 't' ? ', expires at ' : ', valid after ').$stamp if $stamp; 5027 5229 5028 5230 local $dbh->{AutoCommit} = 0;
Note:
See TracChangeset
for help on using the changeset viewer.
![[ DNS Administrator ]](/fx/dnsadmin-logo.png)