From 59cc9376b93be719a8851042838de29284c9a57f Mon Sep 17 00:00:00 2001 From: Matt Zagrabelny Date: Thu, 26 Jan 2017 11:34:49 -0600 Subject: [PATCH] add case insensitive searching for zone names Domain names are case insensitive. Searches for a mixed case zone and a lowercase zone with the same symbols (letters) should return the same zone object. This point will be particularly evident when searching for IPv6 arpa domains (zones) created before commit 9f650144017d when the default for NetAddr::IP would create v6 addresses in upper case - thus an uppercase ip6.arpa zone. Post commit 9f650144017d lowercase zones would be searched for - thus a search miss would occur. Additionally this commit adds tests for case insensitive zone searches, as well as alias searches. Lastly, whitespace was modified for the Zone::search subroutine to elimitate trailing spaces and translate tabs to 4 spaces. --- lib/Netdot/Model.pm | 1 + lib/Netdot/Model/Zone.pm | 112 +++++++++++++++++++++------------------ t/Zone.t | 23 +++++++- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/lib/Netdot/Model.pm b/lib/Netdot/Model.pm index 009c86e58..1b98b9b18 100644 --- a/lib/Netdot/Model.pm +++ b/lib/Netdot/Model.pm @@ -6,6 +6,7 @@ use Time::Local; use Net::DNS; use Digest::MD5 qw(md5_hex); use Scalar::Util qw(blessed); +use Class::DBI::AbstractSearch; =head1 NAME diff --git a/lib/Netdot/Model/Zone.pm b/lib/Netdot/Model/Zone.pm index 353bdcd53..657b356cf 100644 --- a/lib/Netdot/Model/Zone.pm +++ b/lib/Netdot/Model/Zone.pm @@ -24,41 +24,43 @@ DNS Zone Class We override the base method to add functionality: - - Return the most specific domain name. - - If given: + - Return the most specific domain name. + - Perform case-insensitive searches for the name of the zone. + + If given: - name=>dns.cs.local.domain + name => dns.cs.local.domain - we will look for a Zone object in this order: + we will look for a Zone object in this order: dns.cs.local.domain (not found) cs.local.domain (not found) local.domain (found) + return 'local.domain' - Search the ZoneAlias table in addition to the Zone table. - Arguments: - Hash with key/value pairs - Returns: - See Class::DBI search - Examples: - Zone->search(name=>'some.domain.name') + Arguments: + Hash with key/value pairs + Returns: + See Class::DBI search + Examples: + my $zone_obj = Zone->search(name => 'some.domain.name'); =cut sub search { my ($class, @args) = @_; $class->isa_class_method('search'); - + @args = %{ $args[0] } if ref $args[0] eq "HASH"; - my $opts = @args % 2 ? pop @args : {}; + my $opts = @args % 2 ? pop @args : {}; my %argv = @args; - if ( exists $argv{id} && $argv{id} =~ /\D+/ ){ - # No use searching for non-digits in id field - $argv{id} = 0; + if (exists $argv{id} && $argv{id} =~ /\D+/) { + # No use searching for non-digits in id field + $argv{id} = 0; } my (@result, $result); @@ -69,42 +71,48 @@ sub search { } if (@result || $result) { - return wantarray ? @result : $result; - }elsif ( defined $argv{name} ){ - if ( my $alias = ZoneAlias->search(name=>$argv{name})->first ) { - return $class->SUPER::search(id => $alias->zone->id, $opts); - }elsif ( $argv{name} =~ /\./ && !Ipblock->matches_v4($argv{name}) ){ - my @sections = split '\.', $argv{name}; - - # first try to search for the RFC2317 reverse if it exists - if ( $argv{name} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\.in-addr\.arpa$/o ) { - my $address = join('.',($4, $3, $2, $1)); - if ( my $ipb = Ipblock->search(address=>$address)->first ){ - if ( my $subnet = $ipb->parent ){ - my $subnetaddr = $subnet->address; - my $prefix = $subnet->prefix; - my @octs = split('\.', $subnetaddr); - $argv{name} = $octs[3]."-".$prefix.".$octs[2].$octs[1].$octs[0].in-addr.arpa"; - $logger->debug(sub{ "Zone::search: $argv{name}" }); - if ( $class->SUPER::search(%argv, $opts) ){ - $logger->debug(sub{ "Zone::search: found: ", $argv{name} }); - return $class->SUPER::search(%argv, $opts); - } - } - } - } - while ( @sections ){ - $argv{name} = join '.', @sections; - $logger->debug(sub{ "Zone::search: $argv{name}" }); - if ( $class->SUPER::search(%argv, $opts) ){ - # We call the method again to not mess - # with CDBI's wantarray checks - $logger->debug(sub{ "Zone::search: found: ", $argv{name} }); - return $class->SUPER::search(%argv, $opts); - } - shift @sections; - } - } + return wantarray ? @result : $result; + } + elsif (defined $argv{name}) { + if (my $alias = ZoneAlias->search_where(name => ['lower(name) = ?', lc($argv{name})])->first) { + return $class->SUPER::search(id => $alias->zone->id, $opts); + } + elsif ($argv{name} =~ /\./ && !Ipblock->matches_v4($argv{name})) { + my @sections = split '\.', $argv{name}; + + # first try to search for the RFC2317 reverse if it exists + if ($argv{name} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\.in-addr\.arpa$/) { + my $address = join('.',($4, $3, $2, $1)); + if (my $ipb = Ipblock->search(address => $address)->first) { + if (my $subnet = $ipb->parent) { + my $subnetaddr = $subnet->address; + my $prefix = $subnet->prefix; + my @octs = split('\.', $subnetaddr); + $argv{name} = $octs[3]."-".$prefix.".$octs[2].$octs[1].$octs[0].in-addr.arpa"; + $logger->debug(sub{ "Zone::search: $argv{name}" }); + if ($class->SUPER::search(%argv, $opts)) { + $logger->debug(sub{ "Zone::search: found: ", $argv{name} }); + return $class->SUPER::search(%argv, $opts); + } + } + } + } + while (@sections) { + my $new_name = join '.', @sections; + $argv{name} = [ + 'lower(name) = ?', + lc($new_name), + ]; + $logger->debug(sub{ "Zone::search: $new_name" }); + if ($class->SUPER::search_where(\%argv, $opts)) { + # We call the method again to not mess + # with CDBI's wantarray checks + $logger->debug(sub{ "Zone::search: found: $new_name" }); + return $class->SUPER::search_where(\%argv, $opts); + } + shift @sections; + } + } } return wantarray ? @result : $result; } diff --git a/t/Zone.t b/t/Zone.t index 742fa74a9..25f146eb5 100644 --- a/t/Zone.t +++ b/t/Zone.t @@ -5,8 +5,20 @@ use lib "lib"; BEGIN { use_ok('Netdot::Model::Zone'); } -my $obj = Zone->insert({name=>'domain.name'}); -isa_ok($obj, 'Netdot::Model::Zone', 'insert'); +my $obj = Zone->insert( + { + name => 'domain.name', + }, +); +isa_ok($obj, 'Netdot::Model::Zone', 'insert zone'); + +my $alias = ZoneAlias->insert( + { + name => 'alias.name', + zone => $obj->id, + }, +); +isa_ok($alias, 'Netdot::Model::ZoneAlias', 'insert zone alias'); lives_and { is(Zone->_dot_arpa_to_ip('1.in-addr.arpa'), '1.0.0.0/8', 'IPv4 /8 .arpa zone to address') }; lives_and { is(Zone->_dot_arpa_to_ip('2.1.in-addr.arpa'), '1.2.0.0/16', 'IPv4 /16 .arpa zone to address') }; @@ -28,9 +40,16 @@ lives_and { is(Zone->_dot_arpa_to_ip('c.b.a.9.8.7.6.5.4.3.2.1.ip6.arpa'), '1234: is(Zone->search(name=>'sub.domain.name')->first, $obj, 'search scalar' ); is_deeply([Zone->search(name=>'sub.domain.name')], [$obj], 'search array' ); +is(Zone->search(name=>'sub.DoMaIn.NaMe')->first, $obj, 'case insensitive search scalar' ); +is_deeply([Zone->search(name=>'sub.DoMaIn.NaMe')], [$obj], 'case insensitive search array' ); is(Zone->search(name=>'fake')->first, undef, 'search empty scalar' ); is_deeply([Zone->search(name=>'fake')], [], 'search empty array' ); +is(Zone->search(name=>'alias.name')->first, $obj, 'alias search scalar' ); +is_deeply([Zone->search(name=>'alias.name')], [$obj], 'alias search array' ); +is(Zone->search(name=>'AlIaS.NaMe')->first, $obj, 'case insensitive alias search scalar' ); +is_deeply([Zone->search(name=>'AlIaS.NaMe')], [$obj], 'case insensitive alias search array' ); + is(Zone->search_like(name=>'domain.name')->first, $obj, 'search_like scalar' ); is_deeply([Zone->search_like(name=>'domain.name')], [$obj], 'search_like array' ); is(Zone->search_like(name=>'fake')->first, undef, 'search_like empty scalar' );