#!/usr/bin/env perl # written by Joey Kelly use strict; use warnings; use feature 'say'; use Digest::MD5 qw(md5_hex); use DNS::ZoneParse; use Data::Dumper; $Data::Dumper::Indent = 1; my $server1 = 'roost.flockbox.net'; #my $server1 = 'sustain.joeykelly.net'; #my $server2 = '192.168.2.2'; my $server2 = 'sustain.joeykelly.net'; #my @domainlist = qw(joeykelly.net fredebel.com); my @domainlist = qw(fredebel.com); #@domainlist = qw(16.172.in-addr.arpa.); my @serverlist = qw(roost.flockbox.net sustain.joeykelly.net); foreach my $domain (@domainlist) { say "running checks for $domain"; my $dig1 = get_dig($domain, $serverlist[0]); my $dig2 = get_dig($domain, $serverlist[1]); # if we get lucky and both digs are identical, we bail early my $sums = check_md5($dig1, $dig2); if ($sums) { say "digs match"; #exit; # NOTE: let's continue our tests, in the absense of failed dig matches to test against } =pod # let's deal with arrayrefs from here on out $digarray1 = get_dig_array($dig1); $digarray2 = get_dig_array($dig2); my $soa1 = get_soa($dig1); my $soa2 = get_soa($dig2); check_soa($soa1, $soa2); =cut # let's try the modules #my $zonefile = DNS::ZoneParse->new("/path/to/dns/zonefile.db", $origin); # NOTE: the leading backslash indicates reading from a string instead of from a file my $zonefile1 = DNS::ZoneParse->new(\$dig1, $domain); my $zonefile2 = DNS::ZoneParse->new(\$dig2, $domain); =pod my $SOA = $zonefile1->soa(); #say keys %$SOA; foreach (keys %$SOA) { #say "$_\t$$SOA{$_}"; } say "serial $$SOA{serial}"; say ''; my $NS = $zonefile->ns(); say $NS; say Dumper(@$NS); =cut # let's run our gaggle of tests check_soa($zonefile1, $zonefile2); #say "check_soa($zonefile1, $zonefile2)"; # ___FIXME___ gotta clean up that crap above ASAP # let's work on other tests check_ns($zonefile1, $zonefile2); check_mx($zonefile1, $zonefile2); check_a($zonefile1, $zonefile2); check_cname($zonefile1, $zonefile2); check_txt($zonefile1, $zonefile2); # # UNTESTED check_ptr($zonefile1, $zonefile2); } # subs sub get_dig { my ($domain, $server) = @_; #say "running \$dig = `dig \@$server -t AXFR $domain | grep -Evi 'rrsig|dnskey|nsec|^\$|^;' | sort -u`"; #say ''; #my $dig = `dig \@$server -t AXFR $domain | grep -Evi 'rrsig|dnskey|nsec|^\$|^;' | sort -u`; my $dig = "dig \@$server -t AXFR $domain | grep -Evi 'rrsig|dnskey|nsec|^\$|^;'"; # NOT SORTED, but dupe SOA at the end of the file say "running $dig"; $dig = `$dig`; # NOT SORTED, but dupe SOA at the end of the file # we don't want any DOS line endings, if they ever appear #$dig =~ s|foo|bar|g; $dig =~ s|/r/n|/n|g; chomp $dig; # this returns string data return $dig; } # get dig arrays sub get_dig_array { my $dig = shift; # first we get rid of tabs #$dig =~ s|/t| |g; # NOTE: this isn't having any effect $dig =~ s/\/t/ /g; my @dig = split /\n/, $dig; # this returns dig string data split into an array ref return \@dig; } # we don't care about the actual data, only that we can later compare these contrived arrays in array_compare() sub hash_to_sorted_array { my $hash = shift; my @array; foreach (sort keys %$hash) { push @array, $_; push @array, $$hash{$_}; } return \@array; } sub array_compare { my ($array1, $array2) = @_; #say "passed \$array1:"; #say "\$array1 = " . $array1; #say Dumper($array1); #say Dumper($array2); # quick and dirty Array::Compare internals trick my $one = join '', @$array1; my $two = join '', @$array2; #say "serialized \@1 = $one"; #say "serialized \@2 = $two"; return 1 if $one eq $two; return 0; } # tests sub check_md5 { my ($dig1, $dig2) = @_; my $sum1 = md5_hex($dig1); my $sum2 = md5_hex($dig2); return 1 if $sum1 eq $sum2; return 0; } sub check_soa { my ($zonefile1, $zonefile2) = @_; #say "passed check_soa zonefiles ($zonefile1, $zonefile2)"; say "\nchecking SOA records"; my $SOA1 = $zonefile1->soa(); my $SOA2 = $zonefile2->soa(); #say "Dumper(\$SOA1) = "; #say Dumper($SOA1); #say "Dumper(\$SOA2) = "; #say Dumper($SOA2); # #say keys %$SOA; #foreach (keys %$SOA1) { # say "$_\t$$SOA1{$_}"; #} #say "serial 1 = $$SOA1{serial}"; #say "serial 2 = $$SOA2{serial}"; #say ''; #say "SOAs match" if array_compare($SOA1, $SOA2); #say "SOA1 = " . $SOA1; #say "SOA2 = " . $SOA2; # convert to sorted arrays and compare the strings my $sorted1 = hash_to_sorted_array($SOA1); my $sorted2 = hash_to_sorted_array($SOA2); #say "\$sorted1 = $sorted1"; #say "\$sorted2 = $sorted1"; # arrange this as needed say "SOAs match" if array_compare($sorted1, $sorted2); say "SOAs differ" unless array_compare($sorted1, $sorted2); # what are these for? #my @keys1 = keys %$SOA1; #my @keys2 = keys %$SOA2; #sub hash_to_sorted_array { return 0; } sub check_ns { my ($zonefile1, $zonefile2) = @_; say "\nchecking NS records"; my $NS1 = $zonefile1->ns(); my $NS2 = $zonefile2->ns(); #say Dumper($NS1); #say Dumper($NS2); =pod $VAR1 = [ { 'ttl' => '86400', 'host' => 'ns8.redfishnetworks.com.', 'ORIGIN' => 'fredebel.com.', 'class' => 'IN', 'name' => 'fredebel.com.' } =cut my @list1; foreach (@$NS1) { #say $_; #say keys %$_; #say $$_{host}; push @list1, $$_{host}; #say $$NS1[$_]{host}; } @list1 = sort @list1; my @list2; foreach (@$NS2) { push @list2, $$_{host}; } @list2 = sort @list2; say "NS records match" if array_compare(\@list1, \@list2); say "NS records differ" unless array_compare(\@list1, \@list2); } sub check_mx { my ($zonefile1, $zonefile2) = @_; say "\nchecking MX records"; my $MX1 = $zonefile1->mx(); my $MX2 = $zonefile2->mx(); #say Dumper($MX1); #say Dumper($MX2); =pod $VAR1 = [ { 'priority' => '10', 'class' => 'IN', 'ttl' => '86400', 'ORIGIN' => 'fredebel.com.', 'name' => 'fredebel.com.', 'host' => 'upperroomreport.com.' } =cut my @list1; foreach (@$MX1) { push @list1, "$$_{host}:$$_{priority}"; } @list1 = sort @list1; #say @list1; my @list2; foreach (@$MX2) { push @list2, "$$_{host}:$$_{priority}"; } @list2 = sort @list2; #say @list2; say "MX records match" if array_compare(\@list1, \@list2); say "MX records differ" unless array_compare(\@list1, \@list2); } sub check_a { my ($zonefile1, $zonefile2) = @_; say "\nchecking A records"; my $A1 = $zonefile1->a(); my $A2 = $zonefile2->a(); #say Dumper($A1); #say Dumper($A2); =pod $VAR1 = [ { 'class' => 'IN', 'name' => 'fredebel.com.', 'ORIGIN' => 'fredebel.com.', 'ttl' => '86400', 'host' => '184.178.101.189' } =cut my @list1; foreach (@$A1) { push @list1, "$$_{name}:$$_{host}"; } @list1 = sort @list1; #say @list1; my @list2; foreach (@$A2) { push @list2, "$$_{name}:$$_{host}"; } @list2 = sort @list2; #say @list2; say "A records match" if array_compare(\@list1, \@list2); say "A records differ" unless array_compare(\@list1, \@list2); return 0; } sub check_cname { my ($zonefile1, $zonefile2) = @_; say "\nchecking CNAME records"; my $CNAME1 = $zonefile1->cname(); my $CNAME2 = $zonefile1->cname(); #say Dumper($CNAME1); #say Dumper($CNAME2); my @list1; foreach (@$CNAME1) { push @list1, "$$_{name}:$$_{host}"; } @list1 = sort @list1; #say @list1; my @list2; foreach (@$CNAME2) { push @list2, "$$_{name}:$$_{host}"; } @list2 = sort @list2; #say @list2; say "CNAMEs match" if array_compare(\@list1, \@list2); say "CNAMEs differ" unless array_compare(\@list1, \@list2); } sub check_txt { my ($zonefile1, $zonefile2) = @_; say "\nchecking TXT records"; my $TXT1 = $zonefile1->txt(); my $TXT2 = $zonefile2->txt(); #say Dumper($TXT1); #say Dumper($TXT2); =pod $VAR1 = [ { 'ORIGIN' => 'fredebel.com.', 'class' => 'IN', 'name' => 'fredebel.com.', 'text' => 'google-site-verification=SU_21NN-cMs8XKMC_ZBkJv2inGc8bdJhA8upO765P-M', 'ttl' => '86400' } =cut my @list1; foreach (@$TXT1) { push @list1, "$$_{name}:$$_{text}"; } @list1 = sort @list1; #say @list1; my @list2; foreach (@$TXT2) { push @list2, "$$_{name}:$$_{text}"; } @list2 = sort @list2; #say @list2; say "TXT records match" if array_compare(\@list1, \@list2); say "TXT records differ" unless array_compare(\@list1, \@list2); } sub check_ptr { my ($zonefile1, $zonefile2) = @_; say "\nchecking PTR records"; my $PTR1 = $zonefile1->ptr(); my $PTR2 = $zonefile2->ptr(); #say Dumper($PTR1); #say Dumper($PTR2); =pod $VAR1 = [ { 'ORIGIN' => 'fredebel.com.', 'class' => 'IN', 'name' => 'fredebel.com.', 'text' => 'google-site-verification=SU_21NN-cMs8XKMC_ZBkJv2inGc8bdJhA8upO765P-M', 'ttl' => '86400' } =cut my @list1; foreach (@$PTR1) { push @list1, "$$_{name}:$$_{host}"; } @list1 = sort @list1; #say @list1; my @list2; foreach (@$PTR2) { push @list2, "$$_{name}:$$_{host}"; } @list2 = sort @list2; #say @list2; say "PTR records match" if array_compare(\@list1, \@list2); say "PTR records differ" unless array_compare(\@list1, \@list2); } __END__ a sample run: running checks for fredebel.com running dig @roost.flockbox.net -t AXFR fredebel.com | grep -Evi 'rrsig|dnskey|nsec|^$|^;' running dig @sustain.joeykelly.net -t AXFR fredebel.com | grep -Evi 'rrsig|dnskey|nsec|^$|^;' checking SOA records SOAs differ checking NS records NS records match checking MX records MX records differ checking A records A records differ checking CNAME records CNAMEs match checking TXT records TXT records differ checking PTR records PTR records match