#!/usr/bin/perl -w # # Author: Petter Reinholdtsen # Date: 2002-07-08 # # Check /etc/hosts, and make sure the content matches the information # in DNS. Lookup IP, and check if the names listed in /etc/hosts # maches the one in DNS. It will ignore entries with '# NAGIOSIGNORE' # at the end. use vars qw($use_perl_resolver $debug $returnvalue $nagiosmsg); $debug = 0; $returnvalue = 0; # all ok $nagiosmsg = ""; # Report missing reverse lookup. This will ignore CNAME entries # pointing to the IP address, and report error in these cases. $use_reverse = 0; # Report missing forward-lookup. This will complain on private # network IP addresses using the same name as a DNS entry. $use_forward = 1; eval 'use Net::DNS;'; if ($@) { print "Using /etc/hosts\n" if $debug; $use_perl_resolver = 0; } else { print "Using Net::DNS\n" if $debug; $use_perl_resolver = 1; } $host = '/usr/bin/host'; # Look up ip address, and return ($error status, @DNS names, @DNS addresses) sub dns_lookup_ext { local $address = shift; # Stupid Tru64 Unix give me two copies of the error messages from # host. Throw away one of them. close(STDERR); print "Looking up $address using $host\n" if $debug; my @names = (); my @addresses = (); my $lookup = ""; # Some versions need -i to make sure it uses reverse DNS lookup, # and not /etc/hosts. # This option will confuse 'host' on Irix and HP/UX, and make the # program loop forever. Avoiding it for now [pere 2002-08-06] for $options ("") { open(HOST, "$host $options $address 2>&1 |") || die "Unable to execute host"; while () { $lookup .= $_; chomp; print "host: $_\n" if $debug; push(@names, lc($1)) if (/^Name: (.+)$/); push(@names, lc($1)) if (/^Aliases: (.+)$/); # 10.6.240.129.in-addr.arpa PTR perleporten.uio.no push(@names, lc($1)) if (/\s+PTR\s+(.+)$/); # spheniscus.uio.no has address 129.240.148.19 if (/^\S+ has address (\S+)$/) { print "Match addr $1\n" if $debug; push(@addresses, lc($1)) } # 10.6.240.129.IN-ADDR.ARPA domain name pointer perleporten.uio.no if (/IN-ADDR.ARPA domain name pointer\s+(.+)$/) { print "Match name $1\n" if $debug; push(@names, lc($1)) } push(@addresses, $1) if (/^Address: (.+)$/); push(@addresses, $1) if (/\s+A\s+(\d+.+)$/) } close(HOST); if ($lookup =~ /Usage: /) { # Probably unknown parameter, try again without -i $lookup = ""; } else { last; } } return ("no/bad reply from DNS server", undef, undef) if ($lookup !~ /domain name pointer/ && $lookup !~ /\shas address\s/ && $lookup !~ /\sPTR\s/ && $lookup !~ /Name:/); if ( $address =~ m/^\d+\.\d+\.\d+\.\d+/ && ! grep /$address/, @addresses ) { print "Adding $address to list of addresses\n" if $debug; unshift(@addresses, $address) ; } return (undef, \@names, \@addresses); } sub dns_lookup_int { my $address = shift; print "Looking up $address using Net::DNS\n" if $debug; my @names = (); my @addresses = (); my $res = new Net::DNS::Resolver; my $query; if ($address =~ m/\d+\.\d+\.\d+\.\d+/) { $query = $res->query($address); } else { $query = $res->search($address); } if ($query) { foreach $rr ($query->answer) { print "Type: $rr->type\n" if $debug; if ($rr->type eq "A") { print $rr->address, " - A\n" if $debug; push(@addresses, $rr->address); } if ($rr->type eq "CNAME") { print $rr->cname, " - CNAME\n" if $debug; push(@addresses, $rr->cname); } if ($rr->type eq "PTR") { print $rr->ptrdname, " - PTR\n" if $debug; push(@names, lc($rr->ptrdname)); } } return (undef, \@names, \@addresses); } else { print "query failed: ", $res->errorstring, "\n" if $debug; return ($res->errorstring, (), ()); } } sub dns_lookup { my $entry = shift; if ($use_perl_resolver) { return dns_lookup_int($entry); } else { return dns_lookup_ext($entry); } } sub error { local ($level, $error) = @_; $returnvalue = 1 if ($level =~ /^W$/ && $returnvalue <= 1); $returnvalue = 2 if ($level =~ /^C$/); $nagiosmsg = $nagiosmsg . "
" unless ($nagiosmsg =~ /^$/); $nagiosmsg = $nagiosmsg . "$error"; } sub is_ip_private { my $ip = shift; return 1 if ($ip =~ m/^10\./); return 1 if ($ip =~ m/^192\.168\./); return 0; } sub is_names_ip_matching { local ($ip, @names) = @_; my $level = "W"; # Ignore IPv6 addresses for now. return if ($ip =~ m/:/); # Ignore private network return if (is_ip_private($ip)); my $name; for $name (sort @names) { if ($use_reverse) { # Check reverse my ($retval, $revnames) = dns_lookup($ip); return if ( $retval ); # Ignore unknown IP addresses if ( ! $retval && ! grep /$name/, @{$revnames} ) { error $level, "Incorrect /etc/hosts for $ip: ". "$name not in reverse DNS list"; } } if ($use_forward) { # Check forward my ($retval, $revnames, $forwip); ($retval, $revnames) = dns_lookup($ip); ($retval, undef, $forwip) = dns_lookup($name); print "Forward DNS $name/$ip: ", join(" ", @{$forwip}), "\n" if $debug; # Ignore entry if both IP and hostname fail to resolve in DNS return if ( ! defined $revnames && ! defined $forwip ); if ( ! grep /$ip/, @{$forwip} ) { error $level, "Incorrect /etc/hosts for $ip: ". "IP not in forward DNS list for '$name'"; } } } } sub check_etc_hosts { open(HOSTS, "< /etc/hosts") || die "Unable to open /etc/hosts"; while () { chomp; next if (/# NAGIOSIGNORE$/); # Skip lines marked to be ignored s/\#.+//; # Skip comments next if (/^\s*$/); # Skip empty lines print "Testing $_\n" if $debug; $_ = lc($_); local ($ip, @names) = split(/\s+/); # Skip localhost, it is different on some platforms. next if ($ip eq '127.0.0.1'); is_names_ip_matching($ip, @names); } close(HOSTS); } check_etc_hosts() if ( -f "/etc/hosts" ); if ($nagiosmsg =~ /^$/) { print "/etc/hosts OK\n"; } else { print $nagiosmsg . "\n"; } exit $returnvalue;