#!/usr/bin/perl
#
# Copyright 2006-2012 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
#
# rollchk
#
#	This script checks a rollrec file for problems and inconsistencies.
#
#	File problems include:
#		- file doesn't exist
#		- file isn't a regular file
#
#	Raw problems include:
#		- rollrec name used multiple times
#
#	Cooked problems include:
#		- non-existent zone file
#		- non-existent keyrec file
#		- non-positive maxttl
#		- invalid display value
#		- in rollover, but without a phasestart
#		- KSK checks:
#			- invalid KSK phase
#			- mismatched rollover timestamps
#		- ZSK checks:
#			- invalid ZSK phase
#			- mismatched rollover timestamps
#		- contemporaneous KSK rollover and ZSK rollover
#		- empty administrator field
#		- zone file checks:
#			- file doesn't exist
#			- file is not a regular file
#			- file is zero-length
#		- keyrec file checks:
#			- file doesn't exist
#			- file is not a regular file
#			- file is zero-length
#		- zonename checks
#			- zonename matches the SOA name in the zone file
#			- zonename's keyrec record is a zone record
#		- directory checks
#			- non-existent directory
#			- non-directory directory
#		- invalid logging level
#		- empty zsargs field
#

#
# If we're executing from a packed environment, make sure we've got the
# library path for the packed modules.
#
BEGIN
{
	if($ENV{'PAR_TEMP'})
	{
		unshift @INC, ("$ENV{'PAR_TEMP'}/inc/lib");
	}
}


use strict;

use Getopt::Long qw(:config no_ignore_case_always);

use Net::DNS::SEC::Tools::dnssectools;
use Net::DNS::SEC::Tools::rollrec;
use Net::DNS::SEC::Tools::rolllog;
use Net::DNS::SEC::Tools::rollmgr;
use Net::DNS::SEC::Tools::keyrec;
use Net::DNS::SEC::Tools::tooloptions;

use Net::DNS::ZoneFile::Fast;

#
# Version information.
#
my $NAME   = "rollchk";
my $VERS   = "$NAME version: 1.9.1";
my $DTVERS = "DNSSEC-Tools Version: 1.12";

#######################################################################

#
# Data required for command line options.
#
my $count   = 0;			# Error-count flag.
my $quiet   = 0;			# Quiet flag.
my $verbose = 0;			# Verbose flag.
my $doroll  = 1;			# Roll rollrecs flag.
my $doskip  = 1;			# Skip rollrecs flag.

my %options = ();			# Filled option array.
my @opts =
(
	"roll",				# Check the roll rollrecs.
	"skip",				# Check the skip rollrecs.
	"count",			# Give final error count.
	"quiet",			# Don't give any output.
	"verbose",			# Give lotsa output.
	"Version",			# Display the version number.
	"help",				# Give a usage message and exit.
);


my $errs = 0;				# Count of errors.
my $rollerrs = 0;			# Count of roll rollrec errors.
my $skiperrs = 0;			# Count of skip rollrec errors.

my @rrnames;				# List of rollrecs in the file.

my %rolls = ();				# Names of roll rollrecs.
my %skips = ();				# Names of skip rollrecs.

my $rollcnt  = 0;			# Count of roll rollrecs.
my $skipcnt  = 0;			# Count of skip rollrecs.
my $totalcnt = 0;			# Total count of rollrecs.
my $version  = 0;			# Display the version number.

my $ret;				# Return code from main().

$ret = main();
exit($ret);

#-----------------------------------------------------------------------------
# Routine:	main()
#
# Purpose:	This routine does everything -- checks options, parses the
#		rollrec file, runs checks, and gives the results.  Of course,
#		it has a little help from its friends.
#
sub main()
{
	my $argc = @ARGV;		# Number of command line arguments.
	my $errors = 0;			# Total error count.

	#
	# Check our options.
	#
	GetOptions(\%options,@opts) || usage();
	$count	 = $options{'count'};
	$quiet	 = $options{'quiet'};
	$verbose = $options{'verbose'};
	$version = $options{'Version'};
	$doroll  = 0 if(defined($options{'skip'}));
	$doskip  = 0 if(defined($options{'roll'}));

	#
	# Show the version number if requested
	#
	version() if(defined($options{'Version'}));

	usage() if(defined($options{'help'}));

	#
	# Ensure we were given a rollrec file to check.
	#
	usage() if($argc == 0);

	#
	# Make sure the rollrec file is.
	#
	filechecks($ARGV[0]);

	#
	# Run checks on the raw rollrec file.
	#
	rawchecks($ARGV[0]);

	#
	# Read the rollrec file.
	#
	getrollrecs($ARGV[0]);

	#
	# Run the rollrec checks.
	#
	runchecks();

	#
	# Give error-related messages.
	#
	$errors = $errors + $rollerrs + $skiperrs;
	vprint("\n") if($errors > 0);
	vprint("$rollerrs roll-record errors\n");
	vprint("$skiperrs skip-record errors\n");
	print("$errors errors\n") if($count);

	return($errors);
}

#-----------------------------------------------------------------------------
# Routine:	filechecks()
#
# Purpose:	This routine does some basic checks on a file before
#		proceeding to the internal exam.
#
sub filechecks
{
	my $rrfile = shift;			# Rollrec filename.
	my $ret;				# rollrec_read() return.

	#
	# Basic checks on the rollrec file.
	#
	if(! -e $rrfile)
	{
		print STDERR "rollrec file \"$rrfile\" does not exist\n";
		exit(-1);
	}
	if(! -f $rrfile)
	{
		print STDERR "rollrec file \"$rrfile\" is not a regular file\n";
		exit(-1);
	}
}

#-----------------------------------------------------------------------------
# Routine:	rawchecks()
#
# Purpose:	This routine runs a checks on the raw rollrec file.  It
#		reads the file itself, without relying on the rollrec_
#		interfaces.
#
sub rawchecks
{
	my $rrfile = shift;			# Rollrec filename.

	my @rrlines = ();			# Lines from rollrec file.
	my %names   = ();			# All name values.
	my %rolls   = ();			# "roll" values.
	my %skips   = ();			# "skip" values.

	#
	# Read the rollrec file and save the rollrec names.
	#
	open(RRF,"< $rrfile");
	@rrlines = <RRF>;
	close(RRF);

	#
	# Put the record-starting "roll" and "skip" lines in the appropriate
	# hashes.
	#
	foreach my $line (@rrlines)
	{
		my $key;				# Line's key.
		my $val;				# Line's value.

		#
		# Get the key/value fields of the line.
		#
		$line =~ /^\s*([a-zA-Z_]+)\s+"([a-zA-Z0-9\/\-+_.,: \@\t]*)"/;
                $key = $1;
		$val = $2;

		#
		# Skip lines that don't start a new record.
		#
		next if(($key !~ /^roll$/i) && ($key !~ /^skip$/i));

		#
		# Add the record's rollrec name to the all-names hash.
		#
		$names{$val}++;

		#
		# Add the record's rollrec name to the type-specific hash.
		#
		if($key =~ /^roll$/i)
		{
			$rolls{$val}++;
		}
		elsif($key =~ /^skip$/i)
		{
			$skips{$val}++;
		}
	}

	#
	# Ensure that no rollrec name is used more than once.
	#
	foreach my $key (sort(keys(%names)))
	{
		if($names{$key} > 1)
		{
			$errs++;
			qprint("$key:  multiple rollrecs ($names{$key}) using this as the rollrec name\n");
		}
	}

	#
	# If any rollrec name is used more than once, we'll give an error
	# and exit.
	#
	if($errs)
	{
		qprint("errors found in the raw file; not continuing with further checks\n");
		exit($errs);
	}
}

#-----------------------------------------------------------------------------
# Routine:	getrollrecs()
#
# Purpose:	This routine reads a rollrec file and copies the rollrec
#		records into either the roll hash or the skip hash,
#		depending on each record's type.  Any unrecognized rollrec
#		entries are reported. 
#
sub getrollrecs
{
	my $rrfile = shift;			# Rollrec filename.
	my $ret;				# rollrec_read() return.

	#
	# Make sure this looks like a rollrec file.
	#
	if(dt_filetype($rrfile) ne 'rollrec')
	{
		print STDERR "\"$rrfile\" is not a rollrec file\n";
		exit(-1);
	}

	#
	# Read the rollrec file and save the rollrec names.
	#
	rollrec_lock();
	$ret = rollrec_read($rrfile);
	rollrec_unlock();
	exit(-1) if($ret < 0);

	@rrnames = rollrec_names();

	#
	# Get the rollrecs and add them to the appropriate rollrec list.
	#
	foreach my $rrn (sort(@rrnames))
	{
		my $rr;				# Reference to rollrec.
		my %rollrec;			# Rollrec.
		my $type;			# Rollrec's type.

		#
		# Get this particular rollrec and its type.
		#
		$rr = rollrec_fullrec($rrn);
		%rollrec = %$rr;
		$type = $rollrec{'rollrec_type'};

		#
		# Add the rollrec to one of the rollrec lists.
		#
		if($type eq 'roll')
		{
			$rolls{$rrn} = $rr;
			$rollcnt++;
		}
		elsif($type eq 'skip')
		{
			$skips{$rrn} = $rr;
			$skipcnt++;
		}
		else
		{
			qprint("$rrn:  unknown rollrec type \"$type\"\n");
			$errs++;
		}

		$totalcnt++;
	}
}

#-----------------------------------------------------------------------------
# Routine:	runchecks()
#
# Purpose:	This routine runs a number of checks on the roll and skip
#		rollrecs.
#
sub runchecks
{
	my $rr;					# Rollrec hash reference.
	my %rr;					# Rollrec hash table.

	#
	# Mark an error if no rollrecs were defined.
	#
	if($totalcnt == 0)
	{
		vprint("no rollrecs defined\n");
		$errs++;
		return;
	}

	#
	# Check the roll rollrecs.
	#
	if($doroll)
	{
		foreach my $rrname (sort(keys(%rolls)))
		{
			$rr = $rolls{$rrname};
			$rollerrs += rollrec_validate($rrname,$rr);
		}
	}

	#
	# Check the skip rollrecs.
	#
	if($doskip)
	{
		foreach my $rrname (sort(keys(%skips)))
		{
			$rr = $skips{$rrname};
			$skiperrs += rollrec_validate($rrname,$rr);
		}
	}
}

#-----------------------------------------------------------------------------
# Routine:	rollrec_validate()
#
# Purpose:	This routine checks the validity of a rollrec.
#		These checks are:
#			- KSK checks:
#				- invalid rollover phase
#				- mismatched rollover timestamps
#			- ZSK checks:
#				- invalid rollover phase
#				- mismatched rollover timestamps
#			- contemporaneous KSK and ZSK rolls
#			- empty administrator field
#			- invalid logging level
#			- non-positive maxttl
#			- non-existent rollrec file
#			- invalid display value
#			- zone file checks:
#				- file doesn't exist
#				- file is not a regular file
#				- file is zero-length
#			- keyrec file checks:
#				- file doesn't exist
#				- file is not a regular file
#				- file is zero-length
#			- zonename checks
#				- zonename matches the SOA name in the zone file
#				- zonename's keyrec record is a zone record
#			- directory checks
#				- empty directory field
#				- non-existent directory
#				- non-directory directory
#			- empty zsargs field
#
sub rollrec_validate
{
	my $rrname = shift;			# Name of the rollrec.
	my $rr = shift;				# Rollrec reference.

	my %rr = %$rr;				# The rollrec itself.
	my $errs = 0;				# Error count.

	my $admin;				# Administrator email address.
	my $dir;				# Directory for zone's files.
	my $dispflag;				# Display flag.
	my $krfile;				# Keyrec file.
	my $kskphase;				# Current KSK phase.
	my $kskdate;				# KSK roll date string.
	my $kskrsecs;				# KSK roll timestamp.
	my $loglevel;				# Logging level.
	my $maxttl;				# Maximum TTL.
	my $zonefile;				# Zonefile.
	my $zonename;				# Zone name.
	my $zsargs;				# Zonesigner arguments.
	my $zskphase;				# Current ZSK phase.
	my $zskdate;				# ZSK roll date string.
	my $zskrsecs;				# ZSK roll timestamp.

	#
	# Get the rollrec data.
	#
	$admin	  = $rr{'administrator'};
	$dir	  = $rr{'directory'};
	$dispflag = $rr{'display'};
	$krfile	  = $rr{'keyrec'};
	$kskdate  = $rr{'ksk_rolldate'};
	$kskphase = $rr{'kskphase'};
	$kskrsecs = $rr{'ksk_rollsecs'};
	$loglevel = $rr{'loglevel'};
	$maxttl	  = $rr{'maxttl'};
	$zonefile = $rr{'zonefile'};
	$zonename = $rr{'zonename'};
	$zsargs	  = $rr{'zsargs'};
	$zskdate  = $rr{'zsk_rolldate'};
	$zskphase = $rr{'zskphase'};
	$zskrsecs = $rr{'zsk_rollsecs'};

	#
	# Make sure we've got a valid current KSK phase.
	#
	if(($kskphase < 0) || ($kskphase > 7))
	{
		qprint("$rrname:  invalid kskphase \"$kskphase\"\n");
		$errs++;
	}

	#
	# Ensure our KSK time fields make sense.
	#
	if($kskrsecs > 0)
	{
		my $tempus;			# ksk_rollsecs translation.

		$tempus = gmtime($kskrsecs);
		if($tempus ne $kskdate)
		{
			qprint("$rrname:  ksk_rolldate does not match ksk_rollsecs\n");
			$errs++;
		}
	}

	#
	# Make sure we've got a valid current ZSK phase.
	#
	if(($zskphase < 0) || ($zskphase > 4))
	{
		qprint("$rrname:  invalid zskphase \"$zskphase\"\n");
		$errs++;
	}

	#
	# Ensure our ZSK time fields make sense.
	#
	if($zskrsecs > 0)
	{
		my $tempus;			# zsk_rollsecs translation.

		$tempus = gmtime($zskrsecs);
		if($tempus ne $zskdate)
		{
			qprint("$rrname:  zsk_rolldate does not match zsk_rollsecs\n");
			$errs++;
		}
	}

	#
	# Make sure we aren't rolling KSKs and ZSKs at the same time.
	#
	if(($kskphase > 0) && ($zskphase > 0))
	{
		qprint("$rrname:  invalid contemporaneous rollover (kskphase \"$kskphase\", zskphase \"$zskphase\")\n");
		$errs++;
	}

	#
	# Make sure we've got a non-empty administrator.
	#
	if(defined($rr{'administrator'}) && ($admin eq ""))
	{
		qprint("$rrname:  empty administrator\n");
		$errs++;
	}

	#
	# Make sure we've got a valid logging level.
	#
	if(defined($rr{'loglevel'}) && (rolllog_level($loglevel,0) < 0))
	{
		qprint("$rrname:  invalid loglevel \"$loglevel\"\n");
		$errs++;
	}

	#
	# Make sure we've got a positive maximum TTL.
	#
	if($maxttl < 1)
	{
		qprint("$rrname:  invalid maxttl \"$maxttl\"\n");
		$errs++;
	}

	#
	# Make sure we've got a valid display flag.
	#
	if(defined($dispflag))
	{
		if(($dispflag != 0) && ($dispflag != 1))
		{
			qprint("$rrname:  invalid display flag \"$dispflag\"\n");
			$errs++;
		}
	}

	#
	# Check the keyrec and zone files.
	#
	$errs += checkfile($rrname,"keyrec",$krfile,$dir);
	$errs += checkfile($rrname,"zonefile",$zonefile,$dir);

	#
	# Check the directory.
	#
	$errs += checkdir($rrname,$dir) if(defined($rr{'directory'}));

	#
	# If we're rolling, ensure there's a phasestart.
	#
	if($zskphase > 0)
	{
		if(!defined($rr{'phasestart'}))
		{
			qprint("$rrname:  no phasestart listed for zskphase $zskphase\n");
			$errs++;
		}
	}

	#
	# Make sure the zonename is the zonefile's SOA.
	#
	$errs += chkzone($rrname,$zonename,$zonefile,$krfile);

	#
	# Make sure we've got a non-empty set of zonesigner arguments.
	#
	if(defined($rr{'zsargs'}) && ($zsargs eq ""))
	{
		qprint("$rrname:  empty zsargs\n");
		$errs++;
	}

	return($errs);
}

#-----------------------------------------------------------------------------
# Routine:	checkdir()
#
# Purpose:	Check a directory for validity.  This is defined as:
#
#			- the directory exists
#			- the directory is a directory
#
#		A boolean indicates if the directory is valid (0) or not (1).
#
sub checkdir
{
	my $rrname   = shift;				# Rollrec name.
	my $dname    = shift;				# Directory's name.

	#
	# Check the existence of the directory.
	#
	if(! -e $dname)
	{
		qprint("$rrname:  missing directory \"$dname\"\n");
		return(1);
	}

	#
	# Check the normality of the directory.
	#
	if(! -d $dname)
	{
		qprint("$rrname:  directory \"$dname\" is not a directory\n");
		return(1);
	}

	#
	# Return success.
	#
	return(0);
}

#-----------------------------------------------------------------------------
# Routine:	checkfile()
#
# Purpose:	Check a file for validity.  This is defined as:
#
#			- the file exists
#			- the file is a regular file
#			- the file isn't empty
#
#		If the file isn't an absolute path and the rollrec has a
#		directory specified, then the directory is added to the
#		beginning of the pathname.
#
#		A boolean indicates if the file is valid (0) or not (1).
#
sub checkfile
{
	my $rrname   = shift;				# Rollrec name.
	my $filetype = shift;				# File's type.
	my $fname    = shift;				# File's name.
	my $dir	     = shift;				# File's directory.

	#
	# Add the file's directory, if it's defined and the file isn't
	# an absolute path.
	#
	if(($dir ne "") && ($fname !~ /^\//))
	{
		$fname = "$dir/$fname"
	}

	#
	# Check the existence of the file.
	#
	if(! -e $fname)
	{
		qprint("$rrname:  missing $filetype file \"$fname\"\n");
		return(1);
	}

	#
	# Check the normality of the file.
	#
	if(! -f $fname)
	{
		qprint("$rrname:  $filetype file \"$fname\" is not a regular file\n");
		return(1);
	}

	#
	# Check the non-nullity of the file.
	#
	if(! -s $fname)
	{
		qprint("$rrname:  $filetype file \"$fname\" is of size 0\n");
		return(1);
	}

	#
	# Return success.
	#
	return(0);
}

#-----------------------------------------------------------------------------
# Routine:	chkzone()
#
# Purpose:	Check a zonename for validity.  This is defined as:
#
#			- the zonename matches the SOA name in the zone file
#			- zonename's record in the keyrec file is a zone record
#
sub chkzone
{
	my $rrname = shift;			# Rollrec name.
	my $zonename = shift;			# Zonename to check.
	my $zonefile = shift;			# Rollrec's zone file.
	my $krfile = shift;			# Rollrec's keyrec file.

	my $soaname;				# SOA name from zone file.
	my $errs = 0;				# Error count.

	#
	# If the zone file exists, we'll compare the SOA name to the zone
	# name from the rollrec.
	#
	if(-f $zonefile)
	{
		$soaname = getrr($zonefile,'Net::DNS::RR::SOA');
	   	if($zonename ne $soaname)
		{
			qprint("$rrname:  zonename \"$zonename\" not SOA in zonefile\n");
			$errs++;
		}
	}

	#
	# If the keyrec file exists, we'll compare the zone keyrec's name to
	# the zone name from the rollrec.
	#
	if(-f $krfile)
	{
		my $kr;					# Keyrec reference.

		#
		# Get the zone keyrec from the keyrec file.
		#
		keyrec_read($krfile);
		$kr = keyrec_fullrec($zonename);
		keyrec_close();

		#
		# Check the two zonenames.
		#
		if($kr->{'keyrec_type'} ne 'zone')
		{
			qprint("$rrname:  zonename \"$zonename\" not zone in keyrec file\n");
			$errs++;
		}

	}

	#
	# Return the error count.
	#
	return($errs);
}

#-----------------------------------------------------------------------------
# Routine:	getrr()
#
# Purpose:	Return the value of a zone's resource record.  The first
#		value of the specified record is returned.
#
sub getrr
{
	my $zf = shift;				# Zone file to check.
	my $name = shift;			# Zone's resource name.

	my $rref;				# Ref to resource records.
	my @rrs = ();				# Zone's resource records.

	#
	# Parse the zone for its resource records.
	#
	$rref = Net::DNS::ZoneFile::Fast::parse(file => $zf);
	@rrs = @$rref;

	#
	# Dig the needed hash out of the resource record array and return
	# the name.
	#
	foreach my $rrr (@rrs)
	{
		my %rrh = %$rrr;

		return($rrh{'name'}) if($rrr =~ /$name/);
	}

	#
	# Return an error value.
	#
	return('');
}

#-----------------------------------------------------------------------------
# Routine:	dumpdata()
#
# Purpose:	Show the records we found in the rollrec file.
#
#		This routine is only used during debugging.
#
sub dumpdata
{
	my $name;				# Zone name for looping.

	return if(!$verbose);

	print "roll rollrecs:\n";
	foreach $name (sort(keys(%rolls)))
	{
		print "\t$name\n";
	}
	print "\n";

	print "skip rollrecs:\n";
	foreach $name (sort(keys(%skips)))
	{
		print "\t$name\n";
	}
	print "\n";

}

#-----------------------------------------------------------------------------
# Routine:	qprint()
#
# Purpose:	Print a line of text iff the -quiet option wasn't given.
#
sub qprint
{
	my $line = shift;

	print $line if(!$quiet);
}

#-----------------------------------------------------------------------------
# Routine:	vprint()
#
# Purpose:	Print a line of text iff the -quiet option wasn't given and
#		the -verbose option was.
#
sub vprint
{
	my $line = shift;

	qprint($line) if($verbose);
}

#----------------------------------------------------------------------
# Routine:	version()
#
# Purpose:	Print the version number(s) and exit.
#
sub version
{
	print STDERR "$VERS\n";
	print STDERR "$DTVERS\n";

	exit(0);
}


#-----------------------------------------------------------------------------
# Routine:	usage()
#
sub usage
{
	print STDERR "usage:  rollchk [options] <rollrec-file>\n";
	print STDERR "	options:\n";
	print STDERR "		roll\n";
	print STDERR "		skip\n";
	print STDERR "		count\n";
	print STDERR "		quiet\n";
	print STDERR "		verbose\n";
	print STDERR "		help\n";
	print STDERR "		Version\n";
	exit(0);
}

1;

##############################################################################
#

=pod

=head1 NAME

rollchk - Check a DNSSEC-Tools I<rollrec> file for problems and
inconsistencies.

=head1 SYNOPSIS

  rollchk [-roll | -skip] [-count] [-quiet] [-verbose] [-help] rollrec-file

=head1 DESCRIPTION

This script checks the I<rollrec> file specified by I<rollrec-file> for
problems and inconsistencies.

=head1 TYPES OF CHECKS

There are three types of checks performed by B<rollchk>:  file checks,
"raw" file checks, and I<rollrec> checks.  The checks are performed in that
order, and if any of the group checks fail then B<rollchk> exits.

=head2 File Checks

These checks determine basic information about the I<rollrec> file itself.
Recognized problems are:

=over 4

=item * non-existent rollrec file

The specified I<rollrec> file does not exist.

=item * non-regular rollrec file

The specified I<rollrec> file is not a regular file.

=back

=head2 Raw File Checks

These checks are performed directly on the file contents, rather than by
using the I<rollrec.pm> interfaces.  Recognized problems are:

=over 4

=item * duplicated rollrec names

A I<rollrec> name is not unique. 

=back

=head2 Rollrec Checks

These checks are performed after referencing the file contents with the
the I<rollrec.pm> interfaces.  Recognized problems are:

=over 4

=item * no zones defined

No zones are defined in the specified I<rollrec> file.

=item * invalid KSK rollover phase

A zone has an invalid KSK rollover phase.  These phases may be
0, 1, 2, 3, 4, 5, 6, or 7; any other value is invalid.

=item * mismatch in KSK timestamp data

A zone's KSK roll-seconds timestamp does not translate into the date stored
in its roll-date string.

=item * invalid ZSK rollover phase

A zone has an invalid ZSK rollover phase.  These phases may be
0, 1, 2, 3, or 4; any other value is invalid.

=item * mismatch in ZSK timestamp data

A zone's ZSK roll-seconds timestamp does not translate into the date stored
in its roll-date string.

=item * contemporaneous KSK and ZSK rollovers

A zone has a KSK rollover occurring at the same time as a ZSK rollover.
A zone may only have one rollover phase be non-zero at a time.

=item * in rollover without a phasestart

A zone is currently in rollover, but its I<rollrec> record does
not have a I<phasestart> field.

=item * empty administrator

A zone has an empty administrator field.  This field must contain a non-empty
data value.  The value itself is not parsed for accuracy.

=item * non-existent directory

Several checks are made for a zone's directory.  If the zone has a directory
specified, the directory must exist and it must be an actual directory.

=item * invalid display flag

A zone has an invalid display flag.  This flag may be 0 or 1;
any other value is invalid.

=item * non-positive maxttl

The maximum TTL value must be greater than zero.

=item * zone file checks

Several checks are made for a zone's zone file.  The zone file must exist,
it must be a regular file, and it must not be of zero length.

If the file is not an absolute path and the file's I<rollrec> has a
I<directory> entry, then the directory is prepended to the filename
prior to performing any checks.

=item * keyrec file checks

Several checks are made for a zone's I<keyrec> file.  The I<keyrec> file
must exist, it must be a regular file, and it must not be of zero length.

If the file is not an absolute path and the file's I<rollrec> has a
I<directory> entry, then the directory is prepended to the filename
prior to performing any checks.

=item * zonename checks

Several checks are made for zonename.  The zonename must maatch the SOA
name in the zone file, and the zonename's I<keyrec> record in its I<keyrec>
file must be a zone record.

=item * empty zsargs

A zone has an empty zonesigner-arguments field.  If this field exists, it must
contain a non-empty data value.  The value itself is not parsed for accuracy.

=back

=head1 OPTIONS

=over 4

=item B<-roll>

Only display I<rollrec>s that are active ("roll") records.
This option is mutually exclusive of the B<-skip> option.

=item B<-skip>

Only display I<rollrec>s that are inactive ("skip") records.
This option is mutually exclusive of the B<-roll> option.

=item B<-count>

Display a final count of errors.

=item B<-quiet>

Do not display messages.  This option supersedes the setting of the
B<-verbose> option.

=item B<-verbose>

Display many messages.  This option is subordinate to the B<-quiet> option.

=item B<-Version>

Displays the version information for B<rollchk> and the DNSSEC-Tools package.

=item B<-help>

Display a usage message.

=back

=head1 COPYRIGHT

Copyright 2006-2012 SPARTA, Inc.  All rights reserved.
See the COPYING file included with the DNSSEC-Tools package for details.

=head1 AUTHOR

Wayne Morrison, tewok@users.sourceforge.net

=head1 SEE ALSO

B<lsroll(8)>,
B<rollerd(8)>,
B<rollinit(8)>

B<Net::DNS::SEC::Tools::rollrec.pm(3)>

B<file-rollrec(5)>,
B<keyrec(8)>


=cut
