#Here's a shar file containing two scripts for running from cron to
#check zone data and a modified doc 2.0 for use by the scripts.
#
#Jim Knutson
#knutson@mcc.com
#cs.utexas.edu!milano!knutson
#Wk: (512) 338-3362

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by weber.sw.mcc.com!knutson on Wed Apr 24 10:03:07 CDT 1991
# Contents:  ckdomaincf.sh ckserial.sh
 
echo x - ckdomaincf.sh
sed 's/^@//' > "ckdomaincf.sh" <<'@//E*O*F ckdomaincf.sh//'
#!/bin/sh
# @(#)ckdomaincf.sh	1.3	1/4/91
#
# ckdomaincf - check domain configuration
#
# SYNOPSIS
#	ckdomaincf [ -n ]
#
# ckdomaincf checks the domain configuration.  It determines which
# domains to check by examining /etc/named.boot and checking all domains
# listed on the primary and secondar lines.  However, ckdomaincf ignores
# checking the 0.0.127.in-addr.arpa domain and any domain with unixhosts
# as the data file.  It is assumed that these domain data files will
# contain domain definitions for the unix localhost and will be specific
# to the local definition.
# 
# If the -n flag is specified, no mail will be sent after checking.
# 
BOOTFILE=/etc/named.boot
MAIL=/usr/ucb/Mail
NOTIFY=hostmaster
NOTIFYLEVEL=10	# 10 - any warning or greater
		# 50 - any error or greater
		# 100 - any abort
PATH=$PATH:/usr/local/etc export PATH
TMP=/tmp/.ckd$$

set -- `getopt n $*`
if [ $? != 0 ]; then
	echo usage: ckdomaincf [ -n ]
	exit 2
fi
for i in $*; do
	case $i in
	-n ) MAIL="echo /usr/ucb/Mail"; shift;;
	--)	 shift; break;;
	esac
done

tolower() {
	echo $1 | tr A-Z a-z
}

is_in_addr() {
	if [ `expr $1 : '.*in-addr'` -gt 0 ]; then
		true;
	else
		false;
	fi
}

# strip comments from zone data
stripcomments() {
        sed -e '/^;/d' -e 's/;.*//' $*
}

# get mail address of person in charge of the zone
getpersonincharge() {
	dig soa $1. +pfset=0xa224 | \
        stripcomments | tr a-z A-Z | awk '$3 == "SOA" {print $5}'
}

# convert domain name to mail address
domaintoaddr() {
        echo $1 | sed -e 's/\.$//' -e 's/\./@/'
}

# notify the person in charge of a zone of detected errors
notifypersonincharge() {
	failtype=problems
	if [ $2 -gt 50 ]; then
		failtype=errors
	fi
	if [ $2 -gt 100 ]; then
		failtype=failures
	fi
        MB=`getpersonincharge $1`
	( cat $1.log;
	  echo ""; echo ""; echo ""; echo "Complete log of test follows:"; echo "";
	  cat log.$1.
	) | \
        $MAIL -s "$1 zone configuration $failtype" $NOTIFY `domaintoaddr $MB` 
        echo $1 zone configuration $failtype
	sed 's/^/  /' $1.log
        echo "  $NOTIFY, `domaintoaddr $MB` notified via mail."
	echo ""
}

cd /tmp

trap "rm -f $TMP*; exit 1" 2 3

egrep '^(primary|secondary)' $BOOTFILE >$TMP.domains

while read line; do
	set -- $line

	domainname=`tolower $2`
	if is_in_addr $domainname; then
		parent=arpa.
	else
		parent=
	fi

	# find data file
	while [ "$2" != "" ]; do
		shift
	done
	datafile=$1

	# ignore localhost domain stuff
	if [ $domainname = 0.0.127.in-addr.arpa -o $datafile = unixhosts ]; then
		continue
	fi

	rm -f log.$domainname.
	doc -w -e $domainname. $parent 2>/dev/null >$TMP.docout
	status=$?
	egrep -v '^(Doc-|DIGERR|Done testing)' <$TMP.docout >$domainname.log
	
	if [ $status -gt $NOTIFYLEVEL ]; then
		notifypersonincharge $domainname $status
	fi
	
	rm -f log.$domainname. $domainname.log
done <$TMP.domains

rm -f $TMP*
@//E*O*F ckdomaincf.sh//
chmod u=r,g=r,o=r ckdomaincf.sh
 
echo x - ckserial.sh
sed 's/^@//' > "ckserial.sh" <<'@//E*O*F ckserial.sh//'
#!/bin/sh
# @(#)ckserial.sh	1.4	11/21/90
#
# ckserial - check zone file serial numbers
#
# ckserial checks the serial numbers of zone files to ensure
# that they are sequenced properly.  It keeps a checkpointed
# set of data for comparison purposes.
#
# USAGE
#	ckserial [ bootfile ]
#
# BUGS
#	Assumes optional ttl field for SOA record is not used.

BOOTFILE=/etc/named.boot
CKPTDIR=ckpoint		# relative to 'directory' in bootfile
LOGFILE=/tmp/.ckslog$$
SEDFILE=/tmp/.ckssed$$
MAIL=/usr/ucb/Mail
NOTIFY=hostmaster

# change default bootfile if necessary
if [ "$1" != "" ]; then
	if [ -f $1 ]; then
		BOOTFILE=$1
	else
		echo usage: `basename $0` "[ bootfile ]"
		exit 1
	fi
fi

trap "rm -f /tmp/.cks*$$; exit 1" 2 3

# sed file for massaging zone data
cat >$SEDFILE <<!
/^;/d
s/;.*//
:join
/([^)]*$/N
s/\n[ 	]*/ /
t join
s/(\(.*\))/\1/
!

# strip comments from zone data
stripcomments() {
	sed -e '/^;/d' -e 's/;.*//' $*
}

# list zone files used by boot file
#  secondary zones are not checked.  These are assumed to be correct
#  as far as transfers go because old serial numbers won't (shouldn't)
#  be transferred.  Besides, I'm not sure of the semantics for determining
#  whether the last entry is a file or not.  I suppose a simple test of
#  existence would work though.
getzonefiles() {
	awk '\
	$1 == "cache" || $1 == "CACHE" || \
	$1 == "primary" || $1 == "PRIMARY" {
		printf "%s\n",$NF }' $1
}

# get zone file directory
getzonedir() {
	stripcomments $1 | awk 'BEGIN { dir = "/"; }
	$1 == "directory" || $1 == "DIRECTORY" {
		dir = $2 }
	END {print dir}'
}

# get SOA serial number
getserialnumber() {
	tr a-z A-Z <$1 | sed -f $SEDFILE | awk '$3 == "SOA" { print $6}'
}

# get mail address of person in charge of the zone
getpersonincharge() {
	stripcomments $1 | tr a-z A-Z | awk '$3 == "SOA" {print $5}'
}

# get zone origin
getzone() {
	# get zone from zone data file
	zone=`stripcomments $1 | tr a-z A-Z | awk '$3 == "SOA" {print $1}'`
	# if zone is current origin
	if [ "$zone" = "@" ]; then
		# get zone from bootfile
		zone=`grep $1 $BOOTFILE | awk '{print $2}'`
	fi
	echo $zone
}

# checkpoint a list of files
checkpoint() {
	while [ "$1" != "" ]; do
		# copy file preserving modes/dates
		cp -p $1 $CKPTDIR/$1
		shift
	done
}

# get list of include files from zone file
includefiles() {
	grep -i '^\$include' $1 | awk '{print $2}'
}

# is arg 1 < arg2
#  test integer and floating numbers
lt() {
	awk 'BEGIN{ if ('$1' < '$2') exit 0; else exit 1; }'
}

# compare two files ignoring white space changes
compare() {
	diff -b $1 $2 >/dev/null
}

# log a zonefile error message
errlog() {
	ERRORFILE=$1	# save zone file for error reporting
	echo "$2" 1>&2
	echo "$2" >>$LOGFILE
}

# convert domain name to mail address
domaintoaddr() {
	echo $1 | sed -e 's/\.$//' -e 's/\./@/'
}

# notify the person in charge of a zone of detected errors
notifypersonincharge() {
	MB=`getpersonincharge $1`
	ZONE=`getzone $1`
	$MAIL -s "$ZONE zone configuration error" $NOTIFY `domaintoaddr $MB` <$LOGFILE
	echo "	$NOTIFY, `domaintoaddr $MB` notified via mail."
}

processerrors() {
	# if errors found, notify person in charge
	if [ -f $LOGFILE ]; then
		notifypersonincharge $ERRORFILE

		# truncate error log file
		rm -f $LOGFILE
	fi
}

#########################
# real work starts here #
#########################

# cd to the zone file data directory
cd `getzonedir $BOOTFILE`

ZONEFILES=`getzonefiles $BOOTFILE`
SOAFILES=`grep -il '[ 	]IN[ 	]*SOA[ 	]' $ZONEFILES | stripcomments`
EXIT=0

for f in $SOAFILES; do
	# process any logged errors
	processerrors

	# check for zone file existence
	if [ ! -f $f ]; then
		echo "$BOOTFILE: $f doesn't exist" 1>&2
		continue
	fi

	# if this zone file is new, checkpoint it
	if [ ! -f $CKPTDIR/$f ]; then 
		checkpoint $f
		continue
	fi

	#get serial number of current version
	nserialno=`getserialnumber $f`

	#get serial number of checkpointed version
	oserialno=`getserialnumber $CKPTDIR/$f`

	#if serial number differs, continue
	if [ $nserialno != $oserialno ]; then
		# sanity check first
		if lt $nserialno $oserialno; then
			errlog $f "$f: serial number ($nserialno) < previous ($oserialno)"
			EXIT=1
			continue
		fi

		# checkpoint new zone file and included files
		checkpoint $f `includefiles $f`
		continue
	fi

	#if file differs from ckpoint - error
	if compare $f $CKPTDIR/$f; then
		: all ok
	else
		errlog $f "$f: zone file changed, but serial number didn't"
		EXIT=1
		continue
	fi

	# test included files as well
	for i in `includefiles $f`; do
		# if this zone file is new, checkpoint it
		if [ ! -f $CKPTDIR/$i ]; then 
			checkpoint $i
			continue
		fi

		#if file differs from ckpoint - error
		if compare $i $CKPTDIR/$i; then
			: all ok
		else
			errlog $f "$f included changed zone data from $i, but serial number didn't change"
			EXIT=1
			continue
		fi

		# check for nested includes.  these aren't handled
		# but we don't expect them either.
		if [ "`grep -i '\$include' $i`" != "" ]; then
			errlog $i "include file $i includes other files."
			EXIT=1
		fi
	done
done

# process any remaining errors
processerrors

rm -f /tmp/.cks*$$

exit $EXIT
@//E*O*F ckserial.sh//
chmod u=r,g=r,o=r ckserial.sh
 
exit 0
