File: //usr/lib/nagios/plugins/check_etc_hosts
#!/usr/bin/perl -w
#
# Author: Petter Reinholdtsen <pere@hungry.com>
# 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 (<HOST>) {
$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 . "<br>" 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 (<HOSTS>) {
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;