Changeset 1051 for trunk/DNSDB.pm
Legend:
- Unmodified
- Added
- Removed
-
trunk
- 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
- Property svn:mergeinfo changed
-
trunk/DNSDB.pm
r1036 r1051 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); … … 222 223 force_refresh => 1, 223 224 lowercase => 0, # mangle as little as possible by default 225 # tinker with timestamps if adding or updating a record would 226 # cause overlapping CNAME and other in some way 227 coerce_cname_timestamp => 'none', 224 228 # show IPs and CIDR blocks as-is for reverse zones. valid values are 225 229 # 'none' (default, show natural IP or CIDR) … … 283 287 warn "Bad showrev_arpa setting $self->{showrev_arpa}, using default\n"; 284 288 $self->{showrev_arpa} = 'none'; 289 } 290 if (!grep /$self->{coerce_cname_timestamp}/, ('none','adjust','full')) { 291 warn "Bad coerce_cname_timestamp setting $self->{coerce_cname_timestamp}, using default\n"; 292 $self->{coerce_cname_timestamp} = 'none'; 285 293 } 286 294 … … 612 620 ## 613 621 622 # Check for name collisions relating to CNAMEs. Needs to be called from all other validators. 623 sub _cname_collision { 624 my $self = shift; 625 my $dbh = $self->{dbh}; 626 627 my %args = @_; 628 629 my $hcheck = ($args{revrec} eq 'y' ? ${$args{val}} : ${$args{host}}); 630 my $hfield = ($args{revrec} eq 'y' ? 'val' : 'host'); 631 632 # $hcheck should be normalized by the time this sub is called. Convert to the formal .arpa name for error reporting in reverse zones. 633 my $arpaname = ''; 634 if ($args{revrec} eq 'y') { 635 $arpaname = NetAddr::IP->new($hcheck); 636 ##fixme: more voodoo if global and/or per-user ARPA display mode flag set this way or that 637 $arpaname = _ZONE($arpaname, 'ZONE', 'r', '.').($arpaname->{isv6} ? '.ip6.arpa' : '.in-addr.arpa'); 638 } 639 640 # The record type comparison is the only difference between two passes through this chunk of code. 641 # CNAME records require both passes, where other records only need the second one. Downside is 642 # that returning error messages needs to check the loop variable on top of whatever else it references. 643 foreach my $tcompare ('<>', '=') { 644 next if $tcompare eq '<>' && ${$args{rectype}} != 5; 645 646 # Merging these sets of SQL statements is far too messy and doesn't reasonably 647 # allow for more fine-grained error/warning messages to be returned 648 649 # First lookup fails out collisions with records without timestamps or default records (which can not have timestamps) 650 my $sql = "SELECT count(*) FROM "._rectable($args{defrec}, $args{revrec}). 651 " WHERE "._recparent($args{defrec}, $args{revrec})." = ? AND type $tcompare 5 AND $hfield = ?"; 652 my @lookupargs = ($args{id}, $hcheck); 653 $sql .= " AND stampactive = 'f'" if $args{defrec} eq 'n'; 654 if ($args{update}) { 655 $sql .= " AND record_id <> ?"; 656 push @lookupargs, $args{update}; 657 } 658 my @t = $dbh->selectrow_array($sql, undef, @lookupargs); 659 if ($t[0] > 0) { 660 if ($tcompare eq '<>') { 661 return ('FAIL', "One or more non-CNAME records already exist for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 662 ". CNAME records cannot use the same name as other records."); 663 } else { 664 return ('FAIL', "There is already a CNAME present for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 665 ". Only one CNAME may be present for a given name."); 666 } 667 } 668 669 # By this point, all failure cases for default records have been checked. 670 # Default records cannot carry timestamps, so cannot have timestamp-based collisions 671 return ('OK','OK') if $args{defrec} ne 'n'; 672 673 # Second lookup fails out various timestamp-exists collision cases when adding/updating with a timestamp 674 $sql = "SELECT count(*) FROM "._rectable($args{defrec}, $args{revrec}). 675 " WHERE "._recparent($args{defrec}, $args{revrec})." = ? AND type $tcompare 5 AND $hfield = ?". 676 " AND stampactive = 't'"; 677 @lookupargs = ($args{id}, $hcheck); 678 if (${$args{stamp}} && ${$args{expires}}) { 679 $sql .= " AND expires = ?"; 680 push @lookupargs, ${$args{expires}}; 681 if ($self->{coerce_cname_timestamp} eq 'none') { 682 # no coercion means new valid-after < existing expires or new expires > existing valid-after will fail 683 $sql .= " AND ". (${$args{expires}} eq 'f' ? "expires = 't' AND stamp <= ?)" : "expires = 'f' AND stamp >= ?"); 684 push @lookupargs, ${$args{stamp}}; 685 } 686 } 687 if ($args{update}) { 688 $sql .= " AND record_id <> ?"; 689 push @lookupargs, $args{update}; 690 } 691 @t = $dbh->selectrow_array($sql, undef, @lookupargs); 692 if ($t[0] > 0) { 693 if ($tcompare eq '<>') { 694 return ('FAIL', "One or more non-CNAME records with timestamps already exist for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 695 ". CNAME records must expire before or become valid after any records with the same name."); 696 } else { 697 return ('FAIL', "There is already a CNAME with a timestamp present for ".($args{revrec} eq 'y' ? $arpaname : $hcheck). 698 ". Records with a matching name must expire before or become valid after this CNAME."); 699 } 700 } 701 702 # Third check starts retrieving actual timestamps to see if we need to, 703 # and then if we can, coerce the new/updated record's timestamp to match 704 $sql = "SELECT extract(epoch from stamp),expires,stamp < now() FROM "._rectable($args{defrec}, $args{revrec}). 705 " WHERE "._recparent($args{defrec}, $args{revrec})." = ? AND type $tcompare 5 AND $hfield = ?". 706 " AND stampactive = 't'"; 707 @lookupargs = ($args{id}, $hcheck); 708 if ($args{update}) { 709 $sql .= " AND record_id <> ?"; 710 push @lookupargs, $args{update}; 711 } 712 if (${$args{stamp}}) { 713 $sql .= " ORDER BY stamp ".(${$args{expires}} eq 'f' ? 'ASC' : 'DESC' )." LIMIT 1"; 714 } else { 715 $sql .= " ORDER BY stamp LIMIT 1"; 716 } 717 @t = $dbh->selectrow_array($sql, undef, @lookupargs); 718 if (@t) { 719 # caller requested an expiry time 720 my $reqstamp = str2time(${$args{stamp}}); 721 if (${$args{expires}} eq 'f') { 722 if ($reqstamp > $t[0]) { 723 # do nothing, new record goes valid after the expiring record we found 724 } else { 725 if ($self->{coerce_cname_timestamp} eq 'adjust') { 726 # coerce the valid-after timestamp 727 ${$args{stamp}} = strftime('%Y-%m-%d %H:%M:%S', localtime($t[0])); 728 return ('WARN', $typemap{${$args{rectype}}}." ".($args{update} ? 'updated' : 'added'). 729 " with modified valid-after time; conflicting expiring record found"); 730 } else { 731 # New valid-after overlaps existing expiry, and not configured to adjust it 732 my $fill = ($tcompare eq '<>' ? ' CNAME, another record' : $typemap{${$args{rectype}}}.', a CNAME'); 733 return ('FAIL', "Cannot ".($args{update} ? 'update' : 'add').$fill. 734 " with a valid-after time already exists for this name"); 735 } 736 } # else ($reqstamp < $t[0]) 737 } else { 738 if ($reqstamp < $t[0]) { 739 # do nothing, new record will expire before the one we found 740 } else { 741 if ($self->{coerce_cname_timestamp} eq 'adjust') { 742 if ($t[2] == 1) { 743 # found a valid-after, but it's in the past, so adding an expiring record to match doesn't make 744 # sense since it's effectively expired. 745 ##fixme: should probably remove this case once we get around to stripping valid-after timestamps once exported as active 746 return ('FAIL', "Cannot ".($args{update} ? 'update' : 'add')." ".$typemap{${$args{rectype}}}. 747 ", an existing valid-after record is already active for this name"); 748 } else { 749 # coerce the expiry timestamp 750 ${$args{stamp}} = strftime('%Y-%m-%d %H:%M:%S', localtime($t[0])); 751 return ('WARN', $typemap{${$args{rectype}}}." ".($args{update} ? 'updated' : 'added'). 752 " with modified expiry time; conflicting valid-after record found"); 753 } 754 } else { 755 return ('FAIL', "Cannot ".($args{update} ? 'update' : 'add')." ".$typemap{${$args{rectype}}}. 756 ", another record with an overlapping valid-after timestamp already exists for this name"); 757 } 758 } 759 } # args{expires} ne 'f' 760 } # if @t 761 762 } # each $tcompare 763 764 return ('OK', 'OK'); 765 } # _cname_collision() 766 614 767 ## All of these subs take substantially the same arguments: 615 768 # a hash containing at least the following keys: … … 808 961 return ('FAIL', "The bare zone name may not be a CNAME") if ${$args{host}} eq $pname || ${$args{host}} =~ /^\@/; 809 962 810 ##enhance: Look up the passed value to see if it exists. Ooo, fancy.811 963 return ('FAIL', $errstr) if ! _check_hostname_form(${$args{val}}, ${$args{rectype}}, $args{defrec}, $args{revrec}); 964 812 965 } # $zname !~ .rpz 813 966 } # revzone eq 'n' … … 1666 1819 1667 1820 return ('WARN', join("\n", $errstr, $warnmsg) ) if $warnmsg; 1668 1821 1669 1822 return ('OK','OK'); 1670 1823 } # done ALIAS record … … 2222 2375 $cfg->{lowercase} = $1 if /^lowercase\s*=\s*([a-z01]+)/i; 2223 2376 $cfg->{showrev_arpa} = $1 if /^showrev_arpa\s*=\s*([a-z]+)/i; 2377 $cfg->{coerce_cname_timestamp} = $1 if /^coerce_cname_timestamp\s*=\s*([a-z]+)/i; 2224 2378 $cfg->{template_skip_0} = $1 if /^template_skip_0\s*=\s*([a-z01]+)/i; 2225 2379 $cfg->{template_skip_255} = $1 if /^template_skip_255\s*=\s*([a-z01]+)/i; … … 4761 4915 4762 4916 my $expires = shift || ''; 4763 $expires = 1 if $expires eq 'until'; # Turn some special values into the appropriate booleans. 4764 $expires = 0 if $expires eq 'after'; 4917 $expires = 't' if $expires eq 'until'; # Turn some special values into the appropriate booleans. 4918 $expires = 'f' if $expires eq 'after'; 4919 $expires = 't' if $expires eq '1'; 4920 $expires = 'f' if $expires eq '0'; 4765 4921 my $stamp = shift; 4766 4922 $stamp = '' if !$stamp; # Timestamp should be a string at this point. … … 4772 4928 return ('FAIL', "expires must be 1, 't', or 'until', or 0, 'f', or 'after'") 4773 4929 if ($stamp && !defined($expires)) 4774 || ($stamp && $expires ne ' 0' && $expires ne '1' && $expires ne 't' && $expires ne 'f');4930 || ($stamp && $expires ne 't' && $expires ne 'f'); 4775 4931 4776 4932 # Spaces are evil. … … 4812 4968 host => $host, rectype => $rectype, val => $val, addr => $addr, 4813 4969 dist => \$dist, port => \$port, weight => \$weight, 4970 stamp => \$stamp, expires => \$expires, 4814 4971 fields => \$fields, vallist => \@vallist); 4815 4972 4816 4973 return ($retcode,$retmsg) if $retcode eq 'FAIL'; 4974 4975 # Check for CNAME collisions. 4976 ##fixme: should trim the list of arguments, not all of these should be required for CNAME collision checking 4977 my ($ccode,$cmsg) = _cname_collision($self, defrec => $defrec, revrec => $revrec, id => $id, 4978 host => $host, rectype => $rectype, val => $val, addr => $addr, 4979 dist => \$dist, port => \$port, weight => \$weight, 4980 stamp => \$stamp, expires => \$expires, 4981 fields => \$fields, vallist => \@vallist); 4982 4983 return ($ccode,$cmsg) if $ccode eq 'FAIL'; 4984 4985 # If both the validator and CNAME collision check return warnings, glue them together 4986 if ($ccode eq 'WARN') { 4987 if ($retcode eq 'WARN') { 4988 $retmsg .= "<br>\n$cmsg"; 4989 } else { 4990 $retmsg = $cmsg; 4991 } 4992 $retcode = 'WARN'; 4993 } 4817 4994 4818 4995 # Minor cleanup of invalid DNS labels … … 4873 5050 $logdata{entry} .= "', TTL $ttl"; 4874 5051 $logdata{entry} .= ", location ".$self->getLoc($location)->{description} if $location; 4875 $logdata{entry} .= ($expires ? ', expires at ' : ', valid after ').$stamp if $stamp;5052 $logdata{entry} .= ($expires eq 't' ? ', expires at ' : ', valid after ').$stamp if $stamp; 4876 5053 4877 5054 # Allow transactions, and raise an exception on errors so we can catch it later. … … 4933 5110 4934 5111 my $expires = shift || ''; 4935 $expires = 1 if $expires eq 'until'; # Turn some special values into the appropriate booleans. 4936 $expires = 0 if $expires eq 'after'; 5112 $expires = 't' if $expires eq 'until'; # Turn some special values into the appropriate booleans. 5113 $expires = 'f' if $expires eq 'after'; 5114 $expires = 't' if $expires eq '1'; 5115 $expires = 'f' if $expires eq '0'; 4937 5116 my $stamp = shift; 4938 5117 $stamp = '' if !$stamp; # Timestamp should be a string at this point. … … 4943 5122 return ('FAIL', "expires must be 1, 't', or 'until', or 0, 'f', or 'after'") 4944 5123 if ($stamp && !defined($expires)) 4945 || ($stamp && $expires ne ' 0' && $expires ne '1' && $expires ne 't' && $expires ne 'f');5124 || ($stamp && $expires ne 't' && $expires ne 'f'); 4946 5125 4947 5126 # Spaces are evil. … … 4989 5168 host => $host, rectype => $rectype, val => $val, addr => $addr, 4990 5169 dist => \$dist, port => \$port, weight => \$weight, 5170 stamp => \$stamp, expires => \$expires, 4991 5171 fields => \$fields, vallist => \@vallist, 4992 5172 update => $id); 4993 5173 4994 5174 return ($retcode,$retmsg) if $retcode eq 'FAIL'; 5175 5176 # Check for CNAME collisions. 5177 ##fixme: should trim the list of arguments, not all of these should be required for CNAME collision checking 5178 my ($ccode,$cmsg) = _cname_collision($self, defrec => $defrec, revrec => $revrec, 5179 id => ($defrec eq 'y' ? $oldrec->{group_id} : ($revrec eq 'n' ? $oldrec->{domain_id} : $oldrec->{rdns_id})), 5180 host => $host, rectype => $rectype, val => $val, addr => $addr, 5181 dist => \$dist, port => \$port, weight => \$weight, 5182 stamp => \$stamp, expires => \$expires, 5183 fields => \$fields, vallist => \@vallist, 5184 update => $id); 5185 5186 return ($ccode,$cmsg) if $ccode eq 'FAIL'; 5187 5188 # If both the validator and CNAME collision check return warnings, glue them together 5189 if ($ccode eq 'WARN') { 5190 if ($retcode eq 'WARN') { 5191 $retmsg .= "<br>\n$cmsg"; 5192 } else { 5193 $retmsg = $cmsg; 5194 } 5195 $retcode = 'WARN'; 5196 } 4995 5197 4996 5198 # Minor cleanup of invalid DNS labels … … 5091 5293 $logdata{entry} .= "', TTL $ttl"; 5092 5294 $logdata{entry} .= ", location ".$self->getLoc($location)->{description} if $location; 5093 $logdata{entry} .= ($expires ? ', expires at ' : ', valid after ').$stamp if $stamp;5295 $logdata{entry} .= ($expires eq 't' ? ', expires at ' : ', valid after ').$stamp if $stamp; 5094 5296 5095 5297 local $dbh->{AutoCommit} = 0;
Note:
See TracChangeset
for help on using the changeset viewer.
![[ DNS Administrator ]](/fx/dnsadmin-logo.png)