#! /usr/local/bin/perl
#
#  soac - DNS authority records (SOA) checker
#
#  invoke as:
#	soac [-l msglevel]
#
#  Copyright (C) 1992, 1993 PUUG - Grupo Portugues de Utilizadores
#				   do Sistema UNIX
#	         1992, 1993 FCCN - Fundacao para o Desenvolvimento dos Meios 
#			     	   Nacionais de Calculo Cientifico 	
#
#  Authors: Jorge Frazao de Oliveira <frazao@puug.pt>
#	    Artur Romao <artur@dns.pt>
#
#  This file is part of the DDT package, Version 2.0.
#
#  Permission to use, copy, modify, and distribute this software and its 
#  documentation for any purpose and without any fee is hereby granted, 
#  provided that the above copyright notice appear in all copies.  Neither 
#  PUUG nor FCCN make any representations about the suitability of this
#  software for any purpose.  It is provided "as is" without express or 
#  implied warranty.


# =()<push(@INC, "@<LIBDIR>@");>()=
push(@INC, "/usr/local/lib/ddt/cmd");

require "ddt.pl";

# Default values for SOA timers.
$TopLevel{"Refresh"} = 86400;	# 24h
$TopLevel{"Retry"}   = 7200;	#  2h
$TopLevel{"Expire"}  = 2592000;	# 30d
$TopLevel{"TTL"}     = 345600;	#  4d
$Other{"Refresh"}    = 28800;	#  8h
$Other{"Retry"}      = 7200;	#  2h
$Other{"Expire"}     = 604800;	#  7h
$Other{"TTL"}        = 86400;	#  1d

$diffTopLevel{"Refresh"} = 0;
$diffTopLevel{"Retry"}   = 0;
$diffTopLevel{"Expire"}  = 0;
$diffTopLevel{"TTL"}     = 0;

$diffOther{"Refresh"}    = 0;
$diffOther{"Retry"}      = 0;
$diffOther{"Expire"}     = 0;
$diffOther{"TTL"}        = 0;

$Timeout = 1;

# =()<&read_config("@<SOACONFIG>@");>()=
&read_config("/usr/local/lib/ddt/cmd/SOA-timers");


while (<STDIN>) {
	next if /^;/;			# ignore commented lines

        chop;                           # strip record separator
	@Field = split(/\s+/, $_);	# break the input line

	if (/^\$ORIGIN/) {
		$Origin = $Field[2];	# set to a different origin
	
		next;
	}

    	if (/^[*\.\-0-9A-Za-z]+/) { 
		$LineName = 1;

		$Name = &make_name($Field[1], $Origin);
	}

    	if (/\tIN\tSOA\t/) {
			$saveLine = $_;

			&show_zone($Zone);

			undef %NS;

			$Zone = $Name;

			if ($saveLine =~ /\(\s*$/) {
				chop ($_ = <STDIN>);
			} 		
			else {
				$_ = $saveLine;
			}

			# get the timers for this zone
			$_ =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+).*\)$/;

       			$SerialNo   = $1;
       			$Refresh    = $2;
       			$Retry      = $3;
       			$Expire     = $4;
			$DefaultTTL = $5;
	}
	elsif (/\tIN\tNS\t/) {
		if ($Name eq $Zone) {
			$NS{$Field[$#Field]} = 1;
		}
	}
}

&show_zone($Zone);

exit 0;


sub read_config {
    	local($file) = @_;
	local($value);

        if(open(CONFIG, $file)) { 
	        while (<CONFIG>) {
			next if !/\s*(\d+)/;

			$value = $1;

			$_ = &toupper($_);

			if (/^T_REFRESH/) {
	    			$TopLevel{"Refresh"} = $value;
			}
			elsif (/^T_RETRY/) {
	    			$TopLevel{"Retry"} = $value;
			}
			elsif (/^T_EXPIRE/) {
	    			$TopLevel{"Expire"} = $value;
			}
			elsif (/^T_TTL/) {
		    		$TopLevel{"TTL"} = $value;
			}
			elsif (/^REFRESH/) {
	    			$Other{"Refresh"} = $value;
			}
			elsif (/^RETRY/) {
	    			$Other{"Retry"} = $value;
			}
			elsif (/^EXPIRE/) {
	    			$Other{"Expire"} = $value;
			}
			elsif (/^TTL/) {
	    			$Other{"TTL"} = $value;
			}
			elsif (/^DIFFT_REFRESH/) {
	    			$diffTopLevel{"Refresh"} = $value;
			}
			elsif (/^DIFFT_RETRY/) {
	    			$diffTopLevel{"Retry"} = $value;
			}
			elsif (/^DIFFT_EXPIRE/) {
	    			$diffTopLevel{"Expire"} = $value;
			}
			elsif (/^DIFFT_TTL/) {
	    			$diffTopLevel{"TTL"} = $value;
			}
			elsif (/^DIFFREFRESH/) {
	    			$diffOther{"Refresh"} = $value;
			}
			elsif (/^DIFFRETRY/) {
	    			$diffOther{"Retry"} = $value;
			}
			elsif (/^DIFFEXPIRE/) {
	    			$diffOther{"Expire"} = $value;
			}
			elsif (/^DIFFTTL/) {
	    			$diffOther{"TTL"} = $value;
			}
			elsif (/^TIMEOUT/) {
	    			$Timeout = $value;
			}
		}

	    	delete $opened{$file} && close($file);
    	}
	else {
		warn "Can't open $file: $!\n"; 
	}
}


#
# the absolute value of a number
#
sub abs {
    	local($value) = @_;

	return $value < 0 ? -$value : $value;
}


#
# check each of the timers for their convenience, according to the values read
# from $ConfigFile (or the default ones, listed at the begining of this file
#
sub check_SOA {
	local($zone) = @_;
    	local(@Msg, $msg, $fmt);
	local($rref, $dref, $rret, $dret, $rexp, $dexp, $rttl, $dttl);

    	if ($Level >= 3) {
		if ($zone =~ tr/\./\./ == 1) {	# top level domain 
			$rref = $TopLevel{"Refresh"};
			$dref = $diffTopLevel{"Refresh"};
			
			$rret = $TopLevel{"Retry"};
			$dret = $diffTopLevel{"Retry"};

			$rexp = $TopLevel{"Expire"};
			$dexp = $diffTopLevel{"Expire"};

			$rttl = $TopLevel{"TTL"};
			$dttl = $diffTopLevel{"TTL"};
		}
		else {				# non-top level domain
			$rref = $Other{"Refresh"};
			$dref = $diffOther{"Refresh"};
	
			$rret = $Other{"Retry"};
			$dret = $diffOther{"Retry"};

			$rexp = $Other{"Expire"};
			$dexp = $diffOther{"Expire"};

			$rttl = $Other{"TTL"};
			$dttl = $diffOther{"TTL"};
		}

		$fmt = "%s%-12s %8d [recommended value: %8d]";

	    	if (&abs($Refresh - $rref) gt $dref) {
		  	push(@Msg, 
			     sprintf($fmt, $Lpad[3], "Refresh", $Refresh, $rref));
	    	}

	    	if (&abs($Retry - $rret) gt $dret) {
			push(@Msg,
                             sprintf($fmt, $Lpad[3], "Retry", $Retry, $rret));
	    	}

	    	if (&abs($Expire - $rexp) gt $dexp) {
			push(@Msg,
		  	     sprintf($fmt, $Lpad[3], "Expire", $Expire, $rexp));
	    	}

	    	if (&abs($DefaultTTL - $rttl) gt $dttl) {
			push(@Msg, 
		  	     sprintf($fmt, $Lpad[3], "Default TTL", $DefaultTTL, $rttl));
		}

		if ($#Msg) {
	    		print "  SOA Record";
			foreach $msg ($[ .. $#Msg) {
				print shift(@Msg);
			}

			print "";
		}
	}
}


#
# call dig to see the SOA record for this zone, according to $server
#
sub dig {
	local($server, $zone) = @_;
    	local($exists, $auth);

	chop $server if $server =~ /[.][0-9]*[.]$/;	# cut trailing dot
							# from IP address 

# =()<	$dig = "@<DIG>@";>()=
	$dig = "/usr/local/bin/dig";

    	open(DIG, "$dig @$server $zone SOA +pfset=0x2220 +norec +time=$Timeout 2>&1 |");

	while(<DIG>) {
		chop;

		$exists = 1;	# dig said something...

		if (/^;; flags:.*\baa\b/) {
			$auth = 1;	# authoritative answer
		}

		if (/^; Bad server/ && $Level >= 1) {
	    		return sprintf("%s%-20s [Bad server]", 
				       $Lpad[1], $server);
		}

		if (/Connection (.*)/ &&  $Level >= 4) {
			return sprintf("%s%-20s [Connection %s]",
				       $Lpad[4], $server, $1);
		}

		if (/(\d+)\s*;serial$/ && $SerialNo != $1 && $Level >= 3) {
			return sprintf("%s%-20s [Serial number mismatch (cached:%d, current:%d)]",
				       $Lpad[3], $server, $SerialNo, $1);
		}
	}

	if ((!$exists || !$auth) && $Level >= 1) {
		return sprintf("%s%-20s [Non-authoritative answer]",
			       $LPad[1], $server);
	}
}


sub check_NS {
	local($zone) = @_;
    	local($ns, @Msg, $msg);

	if ($#NS == 1 && $Level >= 1) {	# this is dangerous
		push(@Msg, "$Lpad[1]$zone has only one name server\n");
	}

    	foreach $ns (keys %NS) {		# check each of the name
		if ($msg = &dig($ns, $zone)) {	# servers for authority 
			push(@Msg, $msg);	# information
		}
    	}

	if ($#Msg) {
		print "  Name Servers";

		foreach $msg ($[ .. $#Msg) {
			print shift(@Msg);
		}

		print "";
    	}
}


sub show_zone {
	local($zone) = @_;

	if ($zone ne "") {
    		print "\n ###", &toupper($zone), "###\n";

    		&check_SOA($zone);
    		&check_NS($zone);
	}
}
