#!/usr/bin/perl # check.bind.pl # # (c) 2004 Kim Holburn # # released under the GPL v2 http://www.gnu.org/copyleft/gpl.html # # This script enforces checks a set of bind 9 files # with a dns zone and one or more PTR zones files # Author = $Author: kimh $ # Date = $Date: 2004/03/26 04:48:47 $ # Id = $Id: check.bind.pl,v 1.5 2004/03/26 04:48:47 kimh Exp $ # Revision = $Revision: 1.5 $ # State = $State: Exp $ # Log = $Log: check.bind.pl,v $ # Log = Revision 1.5 2004/03/26 04:48:47 kimh # Log = fixed cname check # Log = # Log = Revision 1.4 2004/03/26 03:40:19 kimh # Log = for public # Log = # Log = Revision 1.3 2004/03/26 03:31:32 kimh # Log = changed help # Log = # Log = Revision 1.2 2004/03/26 03:08:15 kimh # Log = added cname checks # Log = # Log = Revision 1.1 2004/03/26 01:38:26 kimh # Log = # Log = run checks on bind zone config files # Log = sub fail_usage { print "$0: Usage error: @_\n"; if (scalar @_) { print "$0: Error: \n"; map {print " $_\n";} @_; } print < options -v : verbose -i : add extra internal nets for -x test -a : test A records (against PTR records) -p : test PTR records (against A records) -x : show external IPs (outside current IP space) -n : show internal IPs -m : show multiple IPs (machines with multiple IPs) -c : check CNAME records -h|--help : show this screen EOM exit scalar @_ ; } my ($opt_m, $opt_x, $opt_a, $opt_p, $opt_v, $opt_n)= (0,0,0, 0,0,0); my @ips; while ($ARGV=$ARGV[0]) { if ($ARGV eq '-i') { shift @ARGV; if (!($ARGV = $ARGV[0])) { fail_usage ("-i needs an argument"); } if ($ARGV =~ /^-/) { fail_usage ("-i must be followed by an argument"); } @ips = split " ", $ARGV; for my $ipt (@ips) { if ($ipt !~ /^\d{1,3}\.(\d{1,3}\.)?(\d{1,3}\.)?$/) { fail_usage ("extra internal ip has invalid format ($ipt)"); } } } elsif ($ARGV eq '-s' ) { $opt_s = 1; } elsif ($ARGV eq '-m' ) { $opt_m = 1; } # multiple IPs elsif ($ARGV eq '-x' ) { $opt_x = 1; } # external IPs elsif ($ARGV eq '-a' ) { $opt_a = 1; } # A records on elsif ($ARGV eq '-p' ) { $opt_p = 1; } # ptr records on elsif ($ARGV eq '-c' ) { $opt_c = 1; } # cname records on elsif ($ARGV eq '-v' ) { $opt_v ++; } # verbose elsif ($ARGV =~ /^-h$|^--help$/ ) { fail_usage; } elsif ($ARGV =~ /^-/ ) { fail_usage " invalid option ($ARGV)"; } else { last ; } shift @ARGV; } if (!$opt_m && !$opt_x && !$opt_a && !$opt_p && !$opt_v && !$opt_n && !$opt_c) { fail_usage ("no options selected"); } #if (! scalar @ARGV) # { fail_usage ("no files specified"); } #if (! scalar @ARGV) { # @ARGV = ("/etc/bind/db/db.fred.com", # glob ("/etc/bind/db/db.123.45.???")); # if ($opt_v) { print "adding default files : ", join ("\n", @ARGV), "\n"; } #} if ($opt_v) { print "internal ips : \n ", join ("\n ", @ips), "\n"; } my ($lastname, $origin, $ip); my %a; my %ptr; my %cname; sub sortip { map { $_->[0] } sort { @a_f = @$a; @b_f = @$b; $a_f[1] <=> $b_f[1] || $a_f[2] <=> $b_f[2] || $a_f[3] <=> $b_f[3] || $a_f[4] <=> $b_f[4] } map { if ($_ =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/) { [$_, $1, $2, $3, $4]; } else { [$_, 0, 0, 0, 0]; } } @_; } sub handle_line { my $line = shift; if ($line !~ /\sA\s|\sPTR\s|\sCNAME\s/) { next; } if ($line =~ /^\s+/) { if ($lastname eq '@') { next; } if (!$lastname) { print STDERR "bad follow-on line\n"; next; } $line =~ s/^/$lastname\t/; } my ($name, $type, $value) = split " ", $line; if ($type eq "A") { if ($name !~ /\.$/) { $name = "$name.$origin"; } push (@{ $a{$name} }, $value); } elsif ($type eq "PTR") { $name =~ s/(.)\.$origin$/$1/; $value =~ s/(.)\.$origin$/$1/; if ($ip && $name !~ /[^0-9]/) { $name = "$ip$name"; } push (@{ $ptr{$name} }, $value); } elsif ($type eq "CNAME") { if ($name !~ /\.$/) { $name = "$name.$origin"; } if ($value !~ /\.$/) { $value = "$value.$origin"; } push (@{$cname{$name}}, $value); } $lastname = $name; } for $file (@ARGV) { if (!open (FILE, $file)) { print STDERR "Couldn't open file ($file)\n"; next; } $ip = ""; while () { s/;.*$//; if (/^\s*$/) { next; } if (/^\$TTL/) { next; } if (/^\@\s/) { $lastname = '@'; next; } chomp; if (/^\$ORIGIN/) { ($junk, $origin) = split " ", $_, 2; if ($origin =~ /\.in-addr\.arpa\.$/) { $ip = join('.', reverse(grep(!/[a-z-]/, split(/\./, $origin)))) . "."; push @ips, $ip; } next; } s/(\s)IN(\s)/$1$2/; if (/^\$GENERATE/) { my $junk; my ($junk, $range, @vals) = split " ", $_; my $step = $range; if ($range =~ /\//) { $step =~ s#^.*/##; if ($step =~ /[^0-9]/) { $step = 1; } } else { $step = 1; } $range =~ s#/.*$##; my ($start, $stop) = split (/-/, $range); if (!$start || !$stop) { print STDERR "generate error start-stop\n$_\n"; next; } if ($start =~ /[^0-9]/) { print STDERR "generate error start has nondigits\n$_\n"; next; } if ($stop =~ /[^0-9]/) { print STDERR "generate error stop has nondigits\n$_\n"; next; } if ($start >= $stop) { print STDERR "generate error start >= stop\n$_\n"; next; } for ($i=$start; $i<=$stop; $i+=$step) { my $line = join "\t", @vals; $line =~ s/\$/$i/g; handle_line($line); } next; } if (/^\$/) { print STDERR "warning \$ line not handled\n$_\n"; next; } handle_line ($_); } close FILE; } if ($opt_m) { for my $key (keys %a) { if ((my $n = scalar @{ $a{$key} }) > 1) { print "$key = (", join (", ", @{ $a{$key} }), ")\n" ; } } } #for my $key (keys %a) { # { print "$key = (", join (", ", @{ $a{$key} }), ")\n" ; } #} if ($opt_a) { for my $key (keys %a) { for my $val (@{ $a{$key} }) { if (defined ($ptr{$val}) and @{ $ptr{$val} }[0]) { my $add = @{ $ptr{$val} }[0]; if ($add ne $key) { print "$key A $val != $val PTR $add\n"; } } else { print "$key A $val no PTR\n"; } } } } if ($opt_p) { for my $key (keys %ptr) { for my $val (@{ $ptr{$key} }) { if (defined ($a{$val}) and @{ $a{$val} }) { my @grep = grep { $key eq $_ } @{ $a{$val} }; if (! scalar @grep) { print "$key PTR $val != $val A ", join (" ", @{$a{$val}}), "\n"; } } else { print "$key PTR $val no A\n"; } } } } if ($opt_x) { my $xipatt = '^' . join '|^', @ips; $xipatt =~ s/\./\\./g; #print "debug $xipatt\n"; for my $key (keys %a) { for my $val (@{ $a{$key} }) { if ($val !~ /$xipatt/) { print "$key $val external\n"; } } } } if ($opt_c) { for my $key (keys %cname) { if (defined ($a{$key})) { print "\"$key\" CNAME conflict with A record\n"; print " $key A (", join (", ", @{$a{$key}}), ")\n"; print " $key CNAME (", join (", ", @{$cname{$key}}), ")\n"; } for my $val (@{$cname{$key}}) { if (!defined ($a{$val})) { print "No A record for $key CNAME $val\n"; } } } } # (keys %ptr) #for my $key (sortip (keys %ptr)) { # { print "$key = (", join (", ", @{ $ptr{$key} }), ")\n" ; } #}