#!/usr/bin/perl
#
# Copyright 2006-2014 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
#
# trustman
#

#
# 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 Net::DNS;
use Net::DNS::SEC::Tools::conf;
use Net::DNS::SEC::Validator;
use Net::DNS::SEC::Tools::timetrans;
use Net::DNS::Packet;
use Net::SMTP;
use Getopt::Long qw(:config no_ignore_case_always);
use Sys::Syslog;
use IO::File;
use POSIX;
use Data::Dumper;
use File::Temp qw(tempfile);
$Data::Dumper::Purity = 1;

#
# Version information.
#
my $NAME   = "trustman";
my $VERS   = "$NAME version: 2.0.0";
my $DTVERS = "DNSSEC-Tools Version: 2.1";

#
# Detect required Perl modules.
#
use Net::DNS::SEC::Tools::BootStrap;
dnssec_tools_load_mods( 'Date::Parse'	 => "",
			'Net::DNS::SEC'	 => "");

########################################################
#
# Time constants.
#

my $DAYS30 = 2592000;
my $DAYS15 = 129600;
my $HOUR   = 3600;
my $DAY	   = 86400;

########################################################
#
# Option data.
#
my %opts = (
		t => $HOUR,	# default to one hour
		v => 0,		# verbose on
		c => 0		# don't configure files
	   );

my @opts = (
		'a|anchor_data_file=s',
		'c|config=s',
		'd|dtconfig=s',
		'f|foreground|fg',
		'k|dnsval_conf_file=s',
		'h|help',
		'L|syslog',
		'm|mail_contact_addr=s',
		'M|monitor',
		'n|named_conf_file=s',
		'N|no_error',
		'o|root_hints_file=s',
		'p|print',
		'r|resolv_conf_file=s',
		's|smtp_server=s',
		'nomail',
		'S|single_run',
		't|sleeptime=i',
		'T|tmp_dir=s',
		'v|verbose',
		'V|Version',
		'w|hold_time=s',
		'z|zone=s',
		'norevoke',
	   );

#
# Option variables.
#
my $contactaddr;
my $dvfile;
my $holdtime;
my $monitor = '';			# Monitor flag.
my $ncfile;
my $newconf;
my $newkeyfile;				# Holds data about newly detected keys,
my $resfile;
my $rhfile;
my $sleeptime;
my $smtpserver;
my $tmpdir;

my $errs = 0;				# Option error count.

########################################################
#
# Monitoring data.
#

my $monstr = '';			# Monitor string.

my $cnt_dvkeys = 0;			# Count of keys added to dnsval.conf.
my $cnt_nckeys = 0;			# Count of keys added to named.conf.
my $cnt_hdtimedout = 0;			# Count of hold-down timers reached.
my $cnt_hdtimerson = 0;			# Count of hold-down timers in future.
my $cnt_newkeys = 0;			# Count of new keys received.
my $cnt_rmpkeys = 0;			# Count of removed pending new keys.
my $cnt_rmkeys = 0;			# Count of keys removed from zone.
my $cnt_revkeys = 0;			# Count of keys revoked from zone.

########################################################
#
# Data.
#

my %dtconf;				# Contents of dnssec-tools.conf file.

my @zones = ();
my %revzones = ();
my %keystorage;
my %newkeys;

my %remkeys;
my %sleeptimes;
my %active_refresh_times;
my %zone_configfile_map;
my %zone_retry_times;

my $once;
my $norevoke = 0;

my $initrun = 1;

my ($conffileh, $tmpfileh, $tmpfile, $currenttmpdir, $oldsep);

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

main();
exit(0);

#----------------------------------------------------------------------
# Routine:	main()
#
sub main
{
	#
	# Get our options and arguments.
	#
	optsandargs();

	#
	# Determine zones to be managed.
	#
	push @zones, split(/,/,$opts{'z'}) if($opts{'z'});
	for(my $i = 0; $i <=$#zones; $i++)
	{
		$revzones{$zones[$i]} = $i;
	}

	#
	# Loads the newkeys info.
	#
	load_newkeys();

	#
	# Build a new configuration file.
	#
	mknewconf();

	#
	# Retrieve zones to be monitored and their configured trust anchors.
	#
	get_zones_keys(\%keystorage);

	#
	# Start the daemon, if that's what we should do.
	#
	&daemonize if(!$opts{'f'});

	#
	# Check the keys.
	do
	{
		my $newsleeptime;

		checkkeys();

		$newsleeptime = getsleeptime($sleeptime);

		Verbose("sleeping for " . timetrans($newsleeptime) .  "\n") if(! $once);
		sleep($newsleeptime);

	} while(! $once);

	#
	# If we're monitoring, print the contents of the monitor string.
	#
	monreport() if($monitor);
}

#----------------------------------------------------------------------
# Routine:	optsandargs()
#
sub optsandargs
{
	#
	# Parse command-line options.
	#
	GetOptions(\%opts,@opts) || usage(1);

	#
	# Handle some immediate options.
	#
	usage(0)	if($opts{'h'});
	show_version()	if($opts{'V'});

	#
	# Parse the DNSESC-Tools configuration file.
	#
	getdtconf();

	#
	# newkeyfile will hold data about new keys detected,
	# but not yet added to config files (waiting for add_holddown_time
	# to expire).  Read this file if it exists, and write to it
	# any time the %newkeys structure is modified.
	#
	$newkeyfile  = $opts{'a'} ? $opts{'a'} : $dtconf{'taanchorfile'};
	$newconf     = $opts{'c'};
	$resfile     = $opts{'r'} ? $opts{'r'} : $dtconf{'taresolvconffile'};
	$ncfile	     = $opts{'n'} ? $opts{'n'} : $dtconf{'tanamedconffile'};
	$dvfile	     = $opts{'k'} ? $opts{'k'} : $dtconf{'tadnsvalconffile'};
	$contactaddr = $opts{'m'} ? $opts{'m'} : $dtconf{'tacontact'};
	$smtpserver  = $opts{'s'} ? $opts{'s'} : $dtconf{'tasmtpserver'};
	$tmpdir	     = $opts{'T'} ? $opts{'T'} : $dtconf{'tatmpdir'};
	$sleeptime   = $opts{'t'} ? $opts{'t'} : $dtconf{'tasleeptime'};
	$holdtime    = $opts{'w'} ? $opts{'w'} : $dtconf{'taholdtime'};
	$norevoke    = 1 if($opts{'norevoke'});
	$rhfile	     = $opts{'o'} ? $opts{'o'} : '';
	$monitor     = $opts{'M'};
	$once	     = $opts{'S'};

	if(!$dvfile && !$ncfile)
	{
		print STDERR "Error:  a dnsval.conf (-k) file or named.conf (-n) file must be specified.\n";
		$errs++;
	}

	#
	# Use a local root.hints file if we're running in a packed environment.
	#
	if(runpacked())
	{
		$rhfile  = "$ENV{'PAR_TEMP'}/inc/root.hints"  if($rhfile eq '');
		$resfile = "$ENV{'PAR_TEMP'}/inc/resolv.conf" if($resfile eq '');
	}

	#
	# Validate the files we've been given.
	#
	chkfile(1,0,$newkeyfile,"taanchorfile");
	chkfile(1,0,$dvfile,"tadnsvalconffile");
	chkfile(1,0,$ncfile,"tanamedconffile");
	chkfile(1,0,$resfile,"taresolvconffile");
	chkfile(1,0,$rhfile,"taroothintsfile");
	chkfile(0,0,$tmpdir,"tatmpdir");

	#
	# Ensure we have an SMTP server.
	#
	if(!$smtpserver)
	{
		print STDERR "Error:  tasmtpserver is undefined in configuration file and -s not given\n";
		$errs++;
	}

	#
	# Ensure we have a reporting method.
	#
	if(!$contactaddr && !$opts{'L'} && !$opts{'p'})
	{
		print STDERR "Error:  No reporting method chosen; please select -m, -L, or -p.\n";
		$errs++;
	}

	#
	# If we're monitoring, adjust some settings so we're *only*
	# monitoring and files won't be rebuilt.
	#
	if($monitor)
	{
		delete($opts{'v'});
		$newconf = 0;
		$opts{'f'} = 1;
		$once = 1;
	}

	#
	# Give a usage message if there were any errors.
	#
	if($errs)
	{
		print "\n";
		usage(1);
	}

}

#----------------------------------------------------------------------
# Routine:	getdtconf()
#
# Purpose:	Parse the DNSESC-Tools configuration file.  If the -dtconfig
#		option was given, we'll use that instead of the default.
#
sub getdtconf
{
	#
	# Use a local config file if we're running as part of a packed
	# configuration.
	#
	if(runpacked())
	{
		setconffile("$ENV{'PAR_TEMP'}/inc/dnssec-tools.conf");
	}

	#
	# If there's a -dtconfig command line option, we'll use that.
	#
	setconffile($opts{'d'}) if(exists($opts{'d'}));

	#
	# Parse the dnssec-tools.conf file.
	#
	%dtconf = parseconfig();
}

#----------------------------------------------------------------------
# Routine:	load_newkeys()
#
# Purpose:	Loads the newkeys info from a file.
#
sub load_newkeys
{
	my $undefval = $/;

	#
	# If we're only monitoring, we won't worry about the newkey file.
	#
	return if($monitor);

	#
	# Load in the newkeys info from file if available.
	#
	if($newkeyfile eq '')
	{
		print STDERR "no newkey file; use -a option\n";
		exit(2);
	}

	#
	# Load in the newkeys info from file if available.
	#
	if(open(FILE, "< $newkeyfile") == 0)
	{
		print STDERR "can't open newkey file \'$newkeyfile\': $!\n";
		exit(3);
	}

	undef $/;

	eval <FILE>;

	warn "can't recreate newkey data from file: $@" if $@;

	close FILE;
	$/ = $undefval;
}

#----------------------------------------------------------------------
# Routine:	mknewconf()
#
# Purpose:	Create a new configuration file based on our options.
#
sub mknewconf
{
	my $conffile;

	#
	# Only make a new config file if one was specified.
	#
	return if(! $newconf);

	#
	# Open the current config file.
	#
	$conffile = getconffile();
	open(CONF,$conffile) or die "unable to open \"$conffile\".";
	usage(1) unless $newconf;

	#
	# Open the new config file and copy some lines from old to new.
	#
	open(OUT,">$newconf") or die "unable to open \"$newconf\" for writing.";
	while(<CONF>)
	{
		next if(/^tasleeptime/	    && ($opts{'t'}));
		next if(/^taholdtime/	    && ($opts{'w'}));
		next if(/^tasmtpserver/	    && ($opts{'s'}));
		next if(/^tacontact/	    && ($opts{'m'}));
		next if(/^taresolvconffile/ && ($opts{'r'}));
		next if(/^tanamedconffile/  && ($opts{'n'}));
		next if(/^tadnsvalconffile/ && ($opts{'k'}));
		next if(/^taroothintsfile/  && ($opts{'o'}));
		print OUT $_;
	}

	#
	# Copy the option values to the new file.
	#
	print OUT "tasleeptime\t" . $sleeptime . "\n"		if($opts{'t'});
	print OUT "taholdtime\t" . $holdtime . "\n"		if($opts{'w'});
	print OUT "tasmtpserver\t" . $smtpserver . "\n"		if($opts{'s'});
	print OUT "tacontact\t" . $contactaddr . "\n"		if($opts{'m'});
	print OUT "taresolvconffile\t" . $resfile . "\n"	if($opts{'r'});
	print OUT "tanamedconffile\t" . $ncfile . "\n"		if($opts{'n'});
	print OUT "tadnsvalconffile\t" . $dvfile . "\n"		if($opts{'k'});
	print OUT "taroothintsfile\t" . $rhfile . "\n"		if($opts{'o'});

	#
	# Clean up a little.
	#
	close (OUT);
	close (CONF);
}

#----------------------------------------------------------------------
# Routine:	get_zones_keys()
#
# Purpose:	Retrieve zones to be monitored, and their configured trust
#		anchors (keys), from config files (named.conf, dnsval.conf).
#		Create the revzones structure for later use.
#
sub get_zones_keys
{
	#
	# If zones are specified on the command line, we will only check
	# those zones.  Otherwise, check all zones found in config files.
	#
	read_conf_file(\%keystorage, $ncfile, \%zone_configfile_map)   if($ncfile);
	read_dnsval_file(\%keystorage, $dvfile, \%zone_configfile_map) if($dvfile);

	#
	# If @zones exists now, we used only zones from the cmd line,
	# so we're done.  If not, we got zones from config files, and
	# need to populate both @zones and %revzones.
	#
	if(!exists($zones[0]))
	{
		foreach my $z (keys(%keystorage))
		{
			$zones[$#zones + 1] = $z;
			if(!(exists($revzones{$z})))
			{
				$revzones{$z} = $#zones +1;
			}
		}
	}

	if(!@zones)
	{
		print STDERR "No zones to check, exiting....\n";
		exit(4);
	}
}

#----------------------------------------------------------------------
# Routine:	read_conf_file()
#
# Purpose:	Reads in a named.conf style config file pointed to by $file.
#		Looks for trust anchors using $pat and stores key information
#		in $storage.
#
sub read_conf_file
{
	my ($storage, $file, $configmap) = @_;
	my $pat = "trusted-keys";

	#
	# This pattern is looking for a full name.  regexp pulled from Fast.pm.
	#
	my $mfn = qr{[-\w\$\d*]+(?:\.[-\w\$\d]+)*\.?};

	Verbose("Reading and parsing trust keys from $file\n");

	open(FILE, "< $file") or die "can't open config file: $!\n";

	while(<FILE>)
	{
		next if(! /$pat/);

		while(<FILE>)
		{
			last if(/^\s*\};/);

			next if(! /\s*($mfn)\s+(257)\s+(\d+)\s+(\d+)\s+\"(.+)\"\s*;/);

			my $zonename = $1;
			my ($flags, $protocol, $algorithm) = ($2, $3, $4);
			my $key = $5;

			#
			# Lop off trailing dots, but make sure the root
			# isn't an empty string.
			#
			$zonename =~ s/\.$//;
			$zonename = '.' if($zonename eq '');

			if(keys(%revzones))
			{
				#
				# Only store key data from zones we're actually
				# checking (@zones) if zones were supplied on
				# the command line (-z).
				#
				next if(! exists($revzones{$zonename}));

				$key =~ s/[\n\r\s]//g;

				# Need to remember where these keys came from.
				$configmap->{$zonename} = $file;

				my $newstorageobj =
					{
						flags	  => $flags,
						protocol  => $protocol,
						algorithm => $algorithm,
						key	  => $key,
					};

				Verbose(" Found a key for zone \"$zonename\"\n");
				push (@{$storage->{$zonename}}, $newstorageobj);

			}
		}
	}

	close(FILE);
}

#----------------------------------------------------------------------
# Routine:	read_dnsval_file()
#
# Purpose:	Reads in a dnsval.conf style config file pointed to by $file.
#		Looks for trust anchors using $pat and stores key
#		information in $storage.
#
sub read_dnsval_file
{
	my ($storage, $file, $configmap) = @_;
	my $pat = "trust-anchor";

	Verbose("Reading and parsing trust keys from $file\n");

	start_read_config($file);

	my $fh = $conffileh;

	while(read_next_ta_chunk())
	{
		s/\s;\s*$//;

		next if(! s/^\s*(\S*)\s*$pat\s*//);

		my $trustanchor_type = $1;

		while($_ ne '')
		{
			next if(s/^[\n\r]\s*//);
			next if(s/^\s*#[^\n\r]*[\n\r]*//);

#				last if(! s/^\s*(\S+)\s+("*[^"]+"|\S+)\s*//);
			last if(! s/^\s*(\S+)\s+("+[^"]+"|DS[^\n\r]+)\s*//);

			my ($zonename, $value) = ($1, $2);
			$value =~ s/[\n\r]//g;
			my ($flags, $proto, $algo, $key) = $value =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S[^"]+)/;

			#
			# Lop off trailing dots, but make sure the root
			# isn't an empty string.
			#
			$zonename =~ s/\.$//;
			$zonename = '.' if($zonename eq '');

			if(keys(%revzones))
			{
				#
				# Only store key data from zones we are
				# actually checking (@zones) if zones
				# were supplied on the command line (-z).
				#
				next if(! exists($revzones{$zonename}));

				$configmap->{$zonename} = $file;

				push @{$storage->{$zonename}},
					{
						flags	  => $flags,
						protocol  => $proto,
						algorithm => $algo,
						key	  => $key,
					};

				Verbose(" Found a key for zone \"$zonename\"\n");
				$storage->{$zonename}[$#{$storage->{$zonename}}]{key} =~ s/\s+//g;
			}
			else
			{
				$configmap->{$zonename} = $file;

				push @{$storage->{$zonename}},
					{
						flags	  => $flags,
						protocol  => $proto,
						algorithm => $algo,
						key	  => $key,
					};

				Verbose(" Found a key for zone \"$zonename\"\n");
				$storage->{$zonename}[$#{$storage->{$zonename}}]{key} =~ s/\s+//g;
			}
		}
	}

	$fh->close;
}

#----------------------------------------------------------------------
# Routine:	monreport()
#
# Purpose:	Gve a monitoring report.  This is only called if
#		the -monitor option was given.
#
sub monreport
{
	$monstr = '';

	monvalue($cnt_dvkeys,
		"$cnt_dvkeys key added to dnsval.conf; ",
		"$cnt_dvkeys keys added to dnsval.conf; ");

	monvalue($cnt_nckeys,
		"$cnt_nckeys key added to named.conf; ",
		"$cnt_nckeys keys added to named.conf; ");

	monvalue($cnt_hdtimedout,
		"$cnt_hdtimedout hold-down timer reached; ",
		"$cnt_hdtimedout hold-down timers reached; ");

	monvalue($cnt_hdtimerson,
		"$cnt_hdtimerson hold-down timer in future; ",
		"$cnt_hdtimerson hold-down timers in future; ");

	monvalue($cnt_newkeys,
		"$cnt_newkeys new key received; ",
		"$cnt_newkeys new keys received; ");

	monvalue($cnt_rmpkeys,
		"$cnt_rmpkeys removed pending key; ",
		"$cnt_rmpkeys removed pending keys; ");

	monvalue($cnt_rmkeys,
		"$cnt_rmkeys key removed; ",
		"$cnt_rmkeys keys removed; ");

	monvalue($cnt_revkeys,
		"$cnt_revkeys key revoked; ",
		"$cnt_revkeys keys revoked; ");


	$monstr =~ s/; $//;

	$monstr = "nothing to report" if($monstr eq '');

	print "$monstr\n";
}

#----------------------------------------------------------------------
# Routine:	save_newkeys()
#
# Purpose:	Save any available new-key information.
#
sub save_newkeys
{
	#
	# If we're only monitoring, we won't worry about the newkey file.
	#
	return if($monitor);

	#
	# Load in the newkeys info from file if available.
	#
	if($newkeyfile eq '')
	{
		print STDERR "no newkey file; use -a option\n";
		exit(2);
	}

	#
	# Write the newkeys file whenever it's modified.
	#
	Verbose("Writing new keys to $newkeyfile\n");

	if(open(FILE, "> $newkeyfile") == 0)
	{
		warn "can't open newkeys file \"$newkeyfile\": $!";
		exit(3);
	}

	print FILE Data::Dumper->Dump([\%newkeys], [qw(*newkeys)]);

	close FILE or warn "can't close newkeys file: $!";
}

#----------------------------------------------------------------------
# Routine:	chkfile()
#
# Purpose:	Verify the given file exists and is a file or directory.
#
sub chkfile
{
	my $fflag = shift;            # File/directory flag.
	my $reqd  = shift;            # Required flag.
	my $name  = shift;            # Node name to check.
	my $field = shift;            # Node's description.

	if($reqd && ($name eq ''))
	{
		print STDERR "Error:  $field is not set in configuration file\n";
		$errs++;
		return;
	}

	return if(! $name);

	if(! -e $name)
	{
		print STDERR "Error:  \"$name\" does not exist\n";
		$errs++;
		return;
	}

	if($fflag)
	{
		if(! -f $name)
		{
			print STDERR "Error:  \"$name\" is not a regular file\n";
			$errs++;
		}
	}
	else
	{
		if(! -d $name)
		{
			print STDERR "Error:  \"$name\" is not a directory\n";
			$errs++;
		}
	}
}

#----------------------------------------------------------------------
# Routine:	checkkeys()
#
# Purpose:	Does most of the work for all of trustman.
#
sub checkkeys
{
	my %keys_to_verify;
	my @zones_to_check;
	my $nowsecs = getnowsecs();

	#
	# Copy the keys from the key storage to the list of keys to verify.
	#
	foreach my $k (keys(%keystorage))
	{
		@{$keys_to_verify{$k}} = @{$keystorage{$k}};
	}

	#
	# Check all zones to see if $active_refresh_times{$z} has been reached.
	# We'll also add the zone if this is our first pass.
	#
	foreach my $z (@zones)
	{
		if(($nowsecs >= $active_refresh_times{$z}) || $initrun)
		{
			push @zones_to_check, $z;
		}
	}

	#
	# Build a list of keys to verify.  (I think...)
	#
	checkzones(\@zones_to_check,\%keys_to_verify);

	#
	# All zones have been queried, and queries have been processed.
	#

	#
	# Check timing of new keys.
	#
	timing_check() if(%newkeys);

	#
	# If any keys have reached their hold-down time, remove them from
	# the dnsval.conf and named.conf files.
	#
	removecheck() if(keys(%remkeys));

	#
	# Schedule the remaining trust anchors for hold-down checking.
	#
	sched_remove(\%keys_to_verify);

	$initrun = 0;
}

#----------------------------------------------------------------------
# Routine:	checkzones()
#
# Purpose:	Check zones.
#
sub checkzones
{
	my $ref0 = shift;
	my $ref1 = shift;
	my @zones_to_check = @$ref0;
	my %keys_to_verify = %$ref1;

	Verbose(" Checking zone keys for validity\n");
	foreach my $z (@zones_to_check)
	{
		my $query;
		my %pending;			# Pending new keys.

		#
		# Get the zone's validated DNSKEY record.
		#
		$query = resolve_and_check_dnskey($z,$dvfile);
		if(! $query)
		{
			vnotify("query for keys failed for zone $z\n");

			compute_queryfail_sleepsecs(
						$z,
						$zone_retry_times{$z}{'ottl'},
						$zone_retry_times{$z}{'sigexp'}
					);

			next;
		}

		#
		# Add any new keys to this zone's list of pending new keys.
		#
		if(keys(%newkeys))
		{
			for(my $i = 0; $i <= $#{$newkeys{$z}}; $i++)
			{
				my $keyobj =
				    {
					flags	  => $newkeys{$z}[$i]{flags},
					protocol  => $newkeys{$z}[$i]{protocol},
					algorithm => $newkeys{$z}[$i]{algorithm},
					key	  => $newkeys{$z}[$i]{key},
					found => 0,
				    };
				Verbose("  pending key for zone \"$z\"\n");
				push (@{$pending{$z}}, $keyobj);
			}
		}

		# check the RRSIG over the DNSKEY

		#
		# Calculate the sleep-time and refresh time for this DNSKEY
		# based on one of its RRSIGs.
		#
		my $origttl;
		foreach my $rrsigrec (grep { $_->type eq 'RRSIG' } $query->answer)
		{
			#
			# This assumes that the orig TTLs are always the same
			# (which they should be).
			# XXX: Turn this into a warning if not?
			#
			$origttl = $rrsigrec->orgttl;

			my $sigexp   = $rrsigrec->sigexpiration;
			my $retryobj =	{
						ottl => $origttl,
						sigexp => $sigexp
					};

			$zone_retry_times{$z} = $retryobj;

			my ($rsecs,$rtm) = compute_sleepsecs($origttl,$sigexp);

			$sleeptimes{$z} =  $rsecs;
			$active_refresh_times{$z} = $rtm;

#			Verbose("  $z ...  refresh_secs=$rsecs, refresh_time=$rtm\n");

			#
			# Only need one sleep time per zone so we'll drop out.
			#
			# XXX: But should we have the shortest or the
			# longest?  (In theory they *should* be the same...)
			#
			last;
		}

		if(! $origttl)
		{
			Verbose("No original TTL found for \"$z\"???");
		}

		#
		# If an RRSET is received which does NOT contain a pending
		# new key, remove that new key from the %newkeys.
		#
		foreach my $keyrec (grep { $_->type eq 'DNSKEY' } $query->answer)
		{
			my $ttl = $keyrec->ttl;
			my $key = $keyrec->key;

			next if(! ($keyrec->flags & 1));
			$key =~ s/\s+//g;       # remove all spaces

			#
			# We don't care if a DNSKEY record is found with the
			# revoke bit set unless it's a key we have stored.
			# So, check for a match first.
			#
			if(compare_keys(\%keystorage, $z, $keyrec, $key) != 0)
			{
				my $notnewkey = 0;

				# May be a new key, remember it.
				# Check if this key is already in %newkeys.
				#
				# Also need to find any keys in %newkeys which
				# do NOT appear in a subsequent RRSET.

				if(keys(%newkeys))
				{
					for(my $i = 0; $i <= $#{$newkeys{$z}}; $i++)
					{
						next if(keycmp($newkeys{$z}[$i], $keyrec, $key) == 0);

						$notnewkey = 1;
						next if(!keys(%pending));

						for(my $i = 0; $i <= $#{$pending{$z}}; $i++)
						{
							if(keycmp($pending{$z}[$i], $keyrec, $key))
							{
								$pending{$z}[$i]{found} = 1;
							}
						}
					}
				}

				if(!$notnewkey)
				{
					my $add_holddown_time = compute_add_holddown($origttl, $holdtime);
					my $newkeyobj =
					    {
						flags	  => $keyrec->flags,
						protocol  => $keyrec->protocol,
						algorithm => $keyrec->algorithm,
						key	  => $key,
						holdtime  => $add_holddown_time,
					    };

					Verbose("  adding holddown for new key in $z ($add_holddown_time seconds from now)\n");

					push(@{$newkeys{$z}},$newkeyobj);

					my $notif = "A new key has been received for zone " . $z . ".\n   It will be added when the add holddown time is reached.\n";
					vnotify($notif);
					save_newkeys();
					$cnt_newkeys++;
				}
			}

			#
			# Check if it has the revoke bit set or we aren't
			# doing a revoke.
			#
			elsif(($keyrec->{flags} & 128) && ($norevoke == 0))
			{
				# this key is being revoked
				# print "key being revoked:\n";
				# print_keyrec($keyrec);

				if($dvfile)
				{
					revoke_ta_dnsvalconf($z,$keyrec);
				}
				if($ncfile)
				{
					revoke_ta_namedconf($z,$keyrec);
				}

				#
				# Verify that ALL keys in %keystorage (now
				# %keys_to_verify) were matched.  If a known
				# key disappears, set its remove_holddown timer
				# for removal if it doesn't reappear in time.
			}
			else
			{
				#
				# If this is neither a new key, nor a revoked
				# key if it is a configured trust anchor, delete
				# it from the keys_to_verify structure so we
				# know it is not "removed".
				#
				for(my $i = 0; $i <= $#{$keys_to_verify{$z}}; $i++)
				{
					if(keycmp($keys_to_verify{$z}[$i], $keyrec, $key))
					{
						splice @{$keys_to_verify{$z}},$i,1;
					}
				}

				#
				# If it appears in the %remkeys struct, since
				# it has now reappeared, remove it from remkeys.
				#
				if(keys(%remkeys))
				{
					for(my $i = 0; $i <= $#{$remkeys{$z}}; $i++)
					{
						if(keycmp($remkeys{$z}[$i], $keyrec, $key))
						{
							splice @{$remkeys{$z}},$i,1;
						}
					}
				}
			}

		}

		#
		# Only want to remove pending keys which do not appear in
		# this RRSET if the query was successful.  Will deal with
		# the unsuccessful query below.
		#
		for(my $k = 0; $k <= $#{$pending{$z}}; $k++)
		{
			#
			# Any pending key still not marked found should be
			# removed from %newkeys.
			#
			next if($pending{$z}[$k]{found});

			for(my $j = 0; $j <= $#{$newkeys{$z}}; $j++)
			{
				#
				# Find the entry in newkeys that corresponds
				# to the pending key not found.
				#
				if(keycmp($newkeys{$z}[$j], $pending{$z}[$k]))
				{
					splice @{$newkeys{$z}},$j,1;

					my $notif = "Pending new key for zone " . $z . " has been removed.\n";
					vnotify($notif);
					save_newkeys();
					$cnt_rmpkeys++;
				}
			}
		}
	}

}

#----------------------------------------------------------------------
# Routine:	timing_check()
#
# Purpose:	Check timing of new keys.
#
sub timing_check
{
	my @newkeyzones;

	Verbose("checking new keys for timing\n");

	#
	# If add_holddown_time has been reached, notify.
	#
	my $nowsecs = getnowsecs();

	foreach my $z (keys(%newkeys))
	{
		for(my $i = 0; $i <= $#{$newkeys{$z}}; $i++)
		{
			if($nowsecs >= $newkeys{$z}[$i]{holdtime})
			{
				# notify about this key
				Verbose(" hold-down timer for zone \"$z\" reached (now = $nowsecs > $newkeys{$z}[$i]{holdtime})\n");
mon(" hold-down timer for zone \"$z\" reached (now = $nowsecs > $newkeys{$z}[$i]{holdtime})\n");
				push @newkeyzones, $z;
				$cnt_hdtimedout++;
			}
			else
			{
				Verbose(" hold-down timer for key \"$z\" still in the future (" .  timetrans($newkeys{$z}[$i]{holdtime}- $nowsecs) . ")\n");
mon(" hold-down timer for zone \"$z\" reached (now = $nowsecs > $newkeys{$z}[$i]{holdtime})\n");
				$cnt_hdtimerson++;
			}
		}
	}

	foreach my $z (@newkeyzones)
	{
		#
		# These are all zones for which new keys have reached
		# their add hold-down time.  Add these keys as new trust
		# anchors to the appropriate config files.
		#
		if($ncfile && ($zone_configfile_map{$z} eq $ncfile))
		{
			add_ta_namedconf($z);
		}

		if($dvfile && ($zone_configfile_map{$z} eq $dvfile))
		{
			add_ta_dnsvalconf($z);
		}

		#
		# Now that this key has been added to the appropriate config
		# file(s), put it in keystorage and remove it from newkeys.
		#
		for(my $i =0; $i <= $#{$newkeys{$z}}; $i++)
		{
			my $newstorageobj =
			    {
				flags	  => $newkeys{$z}[$i]{flags},
				protocol  => $newkeys{$z}[$i]{protocol},
				algorithm => $newkeys{$z}[$i]{algorithm},
				key	  => $newkeys{$z}[$i]{key},
			    };
			push (@{$keystorage{$z}}, $newstorageobj);

			splice @{$newkeys{$z}},$i,1;
			save_newkeys();
		}
	}

	#
	# Mail it.
	#
#		if(($contactaddr) && (@newkeyzones))
#		{
#			mailcontact(0,$smtpserver,$contactaddr,@newkeyzones);
#		}

}

#----------------------------------------------------------------------
# Routine:	removecheck()
#
# Purpose:	See if any remkeys have reached their holdtimes.  If so,
#		remove them from the dnsval.conf file and the named.conf file.
#
sub removecheck
{
	my $nowsecs = getnowsecs();

	foreach my $z (keys(%remkeys))
	{
		for(my $i = 0; $i <= $#{$remkeys{$z}}; $i++)
		{
			next if($nowsecs < $remkeys{$z}[$i]{holdtime});

			#
			# Mark this for deletion.
			#
			if($zone_configfile_map{$z} eq $ncfile)
			{
				remove_ta_namedconf($z, $remkeys{$z}[$i]);
			}

			if($zone_configfile_map{$z} eq $dvfile)
			{
				remove_ta_dnsvalconf($z, $remkeys{$z}[$i]);
			}

			#
			# Remove this key from remkeys now, it's been removed.
			#
			splice @{$remkeys{$z}},$i,1;
		}
	}
}

#----------------------------------------------------------------------
# Routine:	sched_remove()
#
# Purpose:	Any zones or keys still in %keys_to_verify did not appear in
#		a query, but are configured trust anchors.  Set the remove
#		hold-down time (30 days) for these keys and add to remkeys
#		for processing on next go.
#
sub sched_remove
{
	my $hr = shift;
	my %keys_to_verify = %$hr;

	foreach my $z (keys(%keys_to_verify))
	{
		my $remove_holddown_time = compute_remove_holddown();

		for(my $i = 0; $i <= $#{$keys_to_verify{$z}}; $i++)
		{
			my $remkeyobj =
			    {
				flags	  => $keys_to_verify{$z}[$i]{flags},
				protocol  => $keys_to_verify{$z}[$i]{protocol},
				algorithm => $keys_to_verify{$z}[$i]{algorithm},
				key	  => $keys_to_verify{$z}[$i]{key},
				holdtime  => $remove_holddown_time,
			    };

			#
			# Only add this key if it isn't already there.
			#
			my $addit = 1;
			if(keys(%remkeys))
			{
				for(my $i = 0; $i <= $#{$remkeys{$z}}; $i++)
				{
					if(keycmp($remkeys{$z}[$i], $remkeyobj))
					{
						$addit = 0;
					}
				}
			}

			if($addit)
			{
				push (@{$remkeys{$z}},$remkeyobj);
			}
		}
	}
}

#----------------------------------------------------------------------
# Routine:	start_read_config()
#
sub start_read_config
{
	my ($currentfile) = @_;

	$oldsep = $/;
	$/ = ";";

	$conffileh = new IO::File;

	Die("Failed to create a file handle for opening $currentfile")
		if(!$conffileh);

	if(!$conffileh->open("$currentfile"))
	{
		Die("Failed to open the file handle for reading $currentfile")
	}
}

#----------------------------------------------------------------------
# Routine:	end_read_config()
#
sub end_read_config
{
	$conffileh->close();
	$/ = $oldsep;
}

#----------------------------------------------------------------------
# Routine:	start_tmpfile()
#
sub start_tmpfile
{
	my ($currentfile) = @_;
	my ($base, $suffix);

	my $newfile = $currentfile;

	$newfile =~ s/(.*)\/([^\/]*)(\.[^\/]*)/$2-XXXXXX/; # contains dir path

	($currenttmpdir, $base, $suffix) = ($1, $2, $3);

	#
	# Doesn't contain directory path.
	#
	if(!$suffix)
	{
		$newfile =~ s/^([^\/]*)(\.[^\/]*)/$1-XXXXXX/;
		($currenttmpdir, $base, $suffix) = ("/tmp",$1, $2);
	}

	if(!$suffix)
	{
		$newfile = "tmpfile-XXXXXX";
		$currenttmpdir = "/tmp";
		$suffix = ".conf";
	}

	$currenttmpdir = $tmpdir if($tmpdir);
	($tmpfileh, $tmpfile) = tempfile($newfile, DIR => $currenttmpdir, SUFFIX => $suffix);

	Die("Failed to open $currenttmpdir/$newfile.$suffix") if(!$tmpfileh);

	start_read_config($currentfile);

	Verbose("Opened $currenttmpdir/$tmpfile to create a replacement for $currentfile\n");

	return($tmpfileh, $tmpfile);
}

#----------------------------------------------------------------------
# Routine:	end_tmpfile()
#
sub end_tmpfile
{
	my ($currentfile) = @_;

	#
	# If we're only monitoring, we won't worry about the tmp file.
	#
	return if($monitor);

	end_read_config($currentfile);
	$tmpfileh->close();

	#
	# Rename TMP to $ncfile.
	#
	my $origname = $currentfile . ".orig";

	if(!rename($currentfile,$origname))
	{
		Die("Fatal Error:  Failed to rename $currentfile to $origname");
	}

	if(!rename($tmpfile,$currentfile))
	{
		Die("Fatal Error:  Failed to rename newly created $tmpfile to $currentfile;\nAn appropriate -T flag or tatmpdir setting may correct this problem.");
	}

	Verbose("Closed $tmpfile and renamed back to $currentfile\n");
	$tmpfileh = undef;
}

#----------------------------------------------------------------------
# Routine:	read_next_ta_chunk()
#
# Purpose:	This parses the dnsval.conf file into pieces.  Specifically:
#		  - Reads in a section
#		  - Assures the bounding ';' was not within a comment
#		  - strips off any leading comments so the first section
#		    should be of the "name type" clause it's looking for
#		    (eg: ": trust-anchor").
#		  - leaves the text in $_
#		  - *iff* $tmpfileh is defined, prints out the stripped parts
#		    to ensure that they're saved back to the tmp file being
#		    created.
#
sub read_next_ta_chunk
{
	$_ = <$conffileh>;

	#
	# The ; separator may be in the middle of a comment unfortunately.
	# read in more lines if so.
	#
	while(/[\n\r]\s*#[^\n\r]*;\s*$/ || /^\s*#[^\n\r]*;\s*$/)
	{
		my $nextline = <$conffileh>;
		last if($nextline eq '');
		$_ .= $nextline;
	}

	#
	# Weed out any comments that occur before the starting line we're
	# looking for.
	#
	while(s/^(\s*#[^\n\r]*[\n\r])//)
	{
		print $tmpfileh $1 if(defined($tmpfileh));
	}

	return($_);
}

#----------------------------------------------------------------------
# Routine:	find_next_ta_chunk_type()
#
# Purpose:	Looks for a particular section of the dnsval.conf file.
#		(Pretty much always "trust-anchor" is likely to be looked for.)
#
sub find_next_ta_chunk_type
{
	my ($type) = @_;

	while(read_next_ta_chunk())
	{

		my $zonefound = 0;
		if(s/^(\s*\S+\s+$type\s*)//)
		{
			# Reprint the segment we just read and smashed.
			print $tmpfileh "$1";

			# Strip the trailing semi.
			s/\s*;\s*$//;

			# Return the rest for processing.
			return($_);
		}
		else
		{
			print $tmpfileh $_;
		}
	}
}

#----------------------------------------------------------------------
# Routine:	add_ta_namedconf()
#
# Purpose:	Add keys to a named.conf file which have been detected from
#		a validated source, and have passed their add_holddown_time.
#
#		Implements Section 2.4.1 of RFC 5011.
#
sub add_ta_namedconf
{
	my $zone = @_;

	return if(!$ncfile);

	start_tmpfile($ncfile);

	while(<$conffileh>)
	{
		print $tmpfileh $_;

		if(/^trusted-keys/)
		{
			print $tmpfileh "\n\n";
			for(my $i =0; $i <= $#{$newkeys{$zone}}; $i++)
			{
				my $newkey = $zone . " " .
					$newkeys{$zone}[$i]{flags} . " " .
					$newkeys{$zone}[$i]{protocol} . " " .
					$newkeys{$zone}[$i]{algorithm} . " " .
					$newkeys{$zone}[$i]{key} . "\";\n";

				print $tmpfileh $newkey;

				notify("New key added to " . $ncfile . " for zone " . $zone . "\n");
				$cnt_nckeys++;
			}
		}
	}

	end_tmpfile($ncfile);
}

#----------------------------------------------------------------------
# Routine:	add_ta_dnsvalconf()
#
# Purpose:	Add keys to a dnsval.conf file which have been detected from
#		a validated source, and have passed their add_holddown_time.
#
#		Implements Section 2.4.1 of RFC 5011.
#
sub add_ta_dnsvalconf
{
	my ($zone) = @_;
	my $pat = "trust-anchor";

	next if(!$dvfile);

	start_tmpfile($dvfile);

	while(find_next_ta_chunk_type($pat))
	{
		my $zonefound = 0;

		#
		# This is just looking to see if the zone we're adding a
		# key for is already in the file (if it's not they've
		# likely set a security expectation that allowed the key to
		# be auto-added even though it's never been secured).
		#
		my $lookingfor = $_;
		while($lookingfor)
		{
			# Skip comments and blank lines.
			next if($lookingfor =~ s/^\s*#[^\n]*\n+//);
			next if($lookingfor =~ s/^\s*\n//);

			#
			# Spot the actual zone-name/data combo.
			#
			$lookingfor =~ s/^\s*(\S+)\s+("*[^"]+"|\S+)\s*//;
			my ($z, $val) = ($1, $2);

			#
			# Strip off the trailing dot from the zone name.
			$z =~ s/\.$//;

			if($z eq $zone)
			{
				$zonefound = 1;
			}
		}

		#
		# Dump the original contents back out; this should preserve
		# lines.
		#
		print $tmpfileh $_,"\n";

		# Print the new keys.
		for(my $i =0; $i <= $#{$newkeys{$zone}}; $i++)
		{
			my $newkeyentry = $zone . ". \"" .
				$newkeys{$zone}[$i]{flags} . " " .
				$newkeys{$zone}[$i]{protocol} . " " .
				$newkeys{$zone}[$i]{algorithm} . " " .
				$newkeys{$zone}[$i]{key} . "\"";

			if($zonefound)
			{
				print $tmpfileh $newkeyentry . $2;
				Verbose("Adding the following key to $dvfile:\n");
				Verbose($newkeyentry . "\n");
				notify("New key added to " . $dvfile . " for zone " . $zone . "\n");
				$cnt_dvkeys++;
			}
			else
			{
				Verbose("Failed to find original zone key in $ncfile!\n");
			}
		}

		print $tmpfileh "\n;";
	}

	end_tmpfile($dvfile);
}

#----------------------------------------------------------------------
# Routine:	remove_ta_dnsvalconf()
#
# Purpose:	Remove keys from a dnsval.conf file.
#		This usually is required when a known key configured as a trust
#		anchor disappears from the query results from a validated
#		response, and remains missing for the required hold time.
#
sub remove_ta_dnsvalconf
{
	my ($zone, $keyrec) = @_;

	my $k	= $keyrec->{key};
	my $f	= $keyrec->{flags};
	my $p	= $keyrec->{protocol};
	my $a	= $keyrec->{algorithm};
	my $pat = "trust-anchor";

	next if(!$dvfile);

	start_tmpfile($dvfile);

	while(find_next_ta_chunk_type($pat))
	{
		while($_ ne '' && s/^\s*(\S+)\s+("*[^"]+"|\S+)\s*//)
		{
			my ($z, $val) = ($1, $2);

			#
			# Strip off the trailing dot from the zone name.
			#
			$z =~ s/\.$//;
			$val =~ s/[\n\r]//g;

			if($z eq $zone)
			{
				my ($flags, $protocol, $algorithm, $key) = $val =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S[^"]+)/;
				$key =~ s/\s+//g;
				$k =~ s/[\n\r]//g;

				if(($k eq $key) &&
				   ($f eq $flags) &&
				   ($p eq $protocol) &&
				   ($a eq $algorithm))
				{
					# It's a match, comment it out.
					print $tmpfileh "# The following key has been removed.\n";

					my $remkeyrec = $z . ". " . $val;
					print $tmpfileh "# " . $remkeyrec . "\n\n";

					notify("The following key has been removed from zone " . $zone . ": " . $remkeyrec . "\n");
					$cnt_rmkeys++;
				}
				else
				{
					#
					# Add the trailing dot when printing
					# the zone name.
					#
					print $tmpfileh $z . ". " . $val . "\n\n";
				}
			}
			else
			{
				#
				# Add the trailing dot when printing zone name.
				#
				print $tmpfileh $z . ". " . $val . "\n\n";
			}
		}

		print $tmpfileh "\n;\n";
	}

	end_tmpfile($dvfile);
}

#----------------------------------------------------------------------
# Routine:	remove_ta_namedconf()
#
# Purpose:	Remove keys from a named.conf file.
#		This usually is required when a known key configured as a trust
#		anchor disappears from the query results from a validated
#		response, and remains missing for the required hold time.
#

sub remove_ta_namedconf
{
	my ($zone, $keyrec) = @_;

	my $key	  = $keyrec->{key};
	my $flags = $keyrec->{flags};
	my $proto = $keyrec->{protocol};
	my $algo  = $keyrec->{algorithm};
	my $pat	  = "^trusted-keys";

	next if(!$ncfile);

	my $trustsection = 0;

	start_tmpfile($ncfile);

	while(<$conffileh>)
	{
		if(s/^\s*$pat\s*//)
		{
			print $tmpfileh "trusted-keys {";
			$trustsection = 1;
			s/\s*\{//;
			if($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
			{
				my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);
				# strip off the trailing dot from the zone name
				$z =~ s/\.$//;
				$k =~ s/\s+//g;
				$k =~ s/\"//g;
				if($z eq $zone)
				{
					$key =~ s/[\n\r]//g;
					$key =~ s/\"//g;
					if(($key eq $k) &&
					   ($flags eq $f) &&
					   ($proto eq $p) &&
					   ($algo eq $a))
					{
						# It's a match, comment it out.
						print $tmpfileh $space;	 # attempting to preserve spacing
						print $tmpfileh "# The following key has been removed.\n";
						my $remkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
						print $tmpfileh "# " . $remkeyrec . "\n";
						notify("The following key has been removed from zone " . $zone . ": " . $remkeyrec . "\n");
						$cnt_rmkeys++;
					}
				}
				else
				{
					# Just print it, it's not the key we're looking for.
					print $tmpfileh $_;
				}
			}
		}
		elsif($trustsection)
		{
			if(/\s*\};/)
			{
				$trustsection = 0;
				print $tmpfileh "\n};\n";
			}
			elsif($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
			{
				my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);

				#
				# Strip off the trailing dot from the zone name.
				#
				$z =~ s/\.$//;
				$k =~ s/\s+//g;
				$k =~ s/\"//g;

				if($z eq $zone)
				{
					$key =~ s/[\n\r]//g;
					$key =~ s/\"//g;

					if(($key eq $k) &&
					   ($flags eq $f) &&
					   ($proto eq $p) &&
					   ($algo eq $a))
					{
						# It's a match, comment it out.
						print $tmpfileh $space; # attempting to preserve spacing
						print $tmpfileh "# The following key has been removed.\n";
						my $remkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
						print $tmpfileh "# " . $remkeyrec . "\n";

						notify("The following key has been removed from zone " . $zone . ": " . $remkeyrec . "\n");
						$cnt_rmkeys++;
					}
					else
					{
						# Just print it, it's not the key we're looking for.
						print $tmpfileh $_;
					}
				}
				else
				{
					# Just print it, it's not the zone we're looking for.
					print $tmpfileh $_;
				}
			}
		}
		else
		{
			print $tmpfileh $_;
		}
	}

	end_tmpfile($ncfile);
}

#----------------------------------------------------------------------
# Routine:	revoke_ta_dnsvalconf()
#
# Purpose:	Revoke keys marked for revocation in a query response
#		from a validated zone.
#
#		Implements Section 2.1 Revocation from RFC 5011.
#
sub revoke_ta_dnsvalconf
{
	my ($zone,$keyrec) = @_;
	my $pat = "trust-anchor";

	next if(!$dvfile);

	start_tmpfile($dvfile);

	while(find_next_ta_chunk_type($pat))
	{
		while($_ ne '' && s/^\s*(\S+)\s+("[^"]+"|[^\n\r]+)\s*//)
		{ #"
			my ($z, $val) = ($1, $2);

			#
			# Strip off the trailing dot from the zone name.
			#
			$z =~ s/\.$//;
			$val =~ s/[\n\r]//g;
			
			if($z ne $zone)
			{
				# Not the revoked zone, put info back in file.
				print $tmpfileh $z . ". " . $val . "\n\n";
				next;
			}

			my ($flags, $protocol, $algorithm, $key) = 
				$val =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S[^"]+)/;

			$key =~ s/\s+//g;

			my $keyin = $keyrec->{key};

			$keyin =~ s/[\n\r]//g;

			#
			# Flag will not match and prob doesn't matter, proto
			# and algo probably do not matter either but I am
			# leaving them in for now.
			#           $keyrec->{flags} eq $flags &&
			if(($keyin eq $key) &&
			   ($keyrec->{protocol} eq $protocol) &&
			   ($keyrec->{algorithm} eq $algorithm))
			{
				# It's a match, comment it out.
				print $tmpfileh "# The following key has been revoked.\n";

				# Give the key new flag values.
				my $revkeyrec = $z . ". \"" . $keyrec->{flags} . " " . $protocol . " " . $algorithm . " " . $key . "\"";
				print $tmpfileh "# " . $revkeyrec . "\n\n";

				notify("The following key has been revoked from zone " . $z . ":\n" . $revkeyrec . "\n");
				$cnt_revkeys++;
			}
			else
			{
				# Not the revoked key, put info back in file.
				print $tmpfileh $z . ". " . $val . "\n\n";
			}

		}

		print $tmpfileh "\n;\n";
	}

	end_tmpfile($dvfile);
}

#----------------------------------------------------------------------
# Routine:	revoke_ta_namedconf()
#
# Purpose:	Revoke keys marked for revocation in a query response
#		from a validated zone.
#
#		Implements Section 2.1 Revocation from RFC 5011.
#
sub revoke_ta_namedconf
{
	my ($zone,$keyrec) = @_;
	my $pat = "^trusted-keys";
	my $trustsection = 0;

	next if(!$ncfile);

	start_tmpfile($ncfile);

	while(<$conffileh>)
	{
		if(s/^\s*$pat\s*//)
		{
			print $tmpfileh "trusted-keys {";
			$trustsection = 1;
			s/\s*\{//;

			if($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
			{
				my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);

				# Strip off the trailing dot from the zone name.
				$z =~ s/\.$//;
				$k =~ s/\s+//g;
				$k =~ s/\"//g;

				if($z eq $zone)
				{
					my $keyin = $keyrec->{key};
					$keyin =~ s/[\n\r]//g;
					if(($keyin eq $k) &&
					   ($keyrec->{flags} eq $f) &&
					   ($keyrec->{protocol} eq $p) &&
					   ($keyrec->{algorithm} eq $a))
					{
						# It's a match, comment it out.
						print $tmpfileh $space; # attempting to preserve spacing
						print $tmpfileh "# The following key has been revoked.\n";

						my $revkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
						print $tmpfileh "# " . $revkeyrec . "\n";

						notify("The following key has been revoked from zone " . $z . ":\n" . $revkeyrec . "\n");
						$cnt_revkeys++;
					}
					else
					{
						# Just print it, it's not the
						# zone we're looking for.
						print $tmpfileh $_;
					}
				}
				else
				{
					# Just print it, it's not the zone
					# we're looking for.
					print $tmpfileh $_;
				}
			}
		}
		elsif($trustsection)
		{

			if(/\s*\};/)
			{
				$trustsection = 0;
				print $tmpfileh "\n};\n";
			}
			elsif($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
			{
				my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);
				# strip off the trailing dot from the zone name
				$z =~ s/\.$//;
				$k =~ s/\s+//g;
				$k =~ s/\"//g;
				if($z eq $zone)
				{
					my $keyin = $keyrec->{key};
					$keyin =~ s/[\n\r]//g;

					if(($keyin eq $k) &&
					   ($keyrec->{flags} eq $f) &&
					   ($keyrec->{protocol} eq $p) &&
					   ($keyrec->{algorithm} eq $a))
					{
						# It's a match, comment it out.
						print $tmpfileh $space; # attempting to preserve spacing
						print $tmpfileh "# The following key has been revoked.\n";
						my $revkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
						print $tmpfileh "# " . $revkeyrec . "\n";
					}
				}
				else
				{
					# Just print it, it's not the key
					# we're looking for.
					print $tmpfileh $_;
				}
			}
		}
		else
		{
			print $tmpfileh $_;
		}
	}

	end_tmpfile($ncfile);
}

#----------------------------------------------------------------------
# Routine:	resolve_and_check_dnskey()
#
# Purpose:	Queries a zone to get the DNSKEY record; returns an answer
#		only if it was validated.  Called by checkzones().
#
sub resolve_and_check_dnskey
{
	my ($z,$file) = @_;
	my $validator;
	my $rq;
    my $pkt = undef;

	Verbose(" Checking the live \"$z\" key\n");

	$validator = new Net::DNS::SEC::Validator(resolv_conf => $resfile,
                          #log_target => "7:stderr",
						  dnsval_conf => $file,
						  root_hints  => $rhfile);
	if(! $validator)
	{
		Verbose("Help! Failed to create validator object using:\n  resolv_conf: \'$resfile\', dnsval_conf: \'$file\', root_hints: \'$rhfile\'\n");
		return(undef);
	}

	$rq = $validator->res_query($z, "IN", "DNSKEY");
	if(! $rq)
	{
		Verbose("Help! resolving failed\n");
	}

	if($validator->isvalidated)
	{
		$pkt = new Net::DNS::Packet(\$rq);
	} 
    else
    {
	    Verbose("Help! Failed to validate keys for \"$z\"\n");
    }

	return($pkt);
}

#----------------------------------------------------------------------
# Routine:	compute_add_holddown()
#
# Purpose:	Used in implementation of Section 2.4.1 of RFC 5011.
#
sub compute_add_holddown
{
	my ($ttl, $default) = @_;
	my $holddown;
	my $nowsecs = getnowsecs();

	#
	# Default to 30 days.
	#
	$default = $DAYS30 if(!$default);

	#
	# Return secs since the epoch as the time to release this holddown.
	#

	#
	# Allow 5 seconds from now; unsafe undocumented debugging feature.
	#
	if($default == -42)
	{
		return($nowsecs + 5);
	}

	#
	# Take the maximum of now+TTL or now+specified-default.
	#
	if($ttl > $default)
	{
		$holddown = $nowsecs + $ttl;
	}
	else
	{
		$holddown = $nowsecs + $default;
	}

	return($holddown);
}


#----------------------------------------------------------------------
# Routine:	compute_remove_holddown()
#
# Purpose:	Used in implementation of Section 2.4.2 of RFC 5011.
#
#		30 days from "now"
#
sub compute_remove_holddown
{
	my $holddown;
	my $default = $DAYS30;
	my $nowsecs = getnowsecs();

	#
	# Return secs since the epoch as the time to release this holddown.
	#
	$holddown = $nowsecs + $default;
	return($holddown);
}

#----------------------------------------------------------------------
# Routine:	compute_sleepsecs()
#
# Purpose:	Compute the sleep time in seconds.
#		min(expiration interval [sigexpiration - now],1/2 * ottl,
#			15 days).
#
#		Implements Section 2.3 of RFC 5011.
#
sub compute_sleepsecs
{
	my ($ottl,$sexp) = @_;

	$sexp =~ s/(....)(..)(..)(..)(..)(..)/$1-$2-$3T$4:$5:$6/;
	my $sigexp = str2time($sexp);

	my $halfottl = $ottl / 2;
	my $nowsecs = getnowsecs();
	my $expinterval = $sigexp - $nowsecs;
	my $actrefsecs;

	if($halfottl < $expinterval)
	{
		if($halfottl < $DAYS15)
		{
			$actrefsecs = $halfottl;
		}
		else
		{
			$actrefsecs = $DAYS15;
		}
	}
	else
	{
		if($expinterval < $DAYS15)
		{
			$actrefsecs = $expinterval;
		}
		else
		{
			$actrefsecs = $DAYS15;
		}
	}

	return($actrefsecs,$actrefsecs+$nowsecs);
}

#----------------------------------------------------------------------
# Routine:	compute_queryfail_sleepsecs()
#
# Purpose:	Compute the number of seconds to sleep in case of a query
#		failure.  The sleep-time is then saved for the specified zone.
#
#		MAX(1 hour, MIN(1 day, 0.1 * ottl, 0.1 *
#				expiration interval[sigexpiration - now])
#
#		Implements Section 2.3 of RFC 5011.
#
sub compute_queryfail_sleepsecs
{
	my ($z,$ottl,$sexp) = @_;
	$sexp =~ s/(....)(..)(..)(..)(..)(..)/$1-$2-$3T$4:$5:$6/;
	my $sigexp = str2time($sexp);
	my $tenth_ottl = $ottl / 10;
	my $nowsecs = getnowsecs();
	my $tenth_expinterval = ($sigexp - $nowsecs) / 10;
	my $refreshsecs;

	if($tenth_ottl < $tenth_expinterval)
	{
		if($tenth_ottl < $DAY)
		{
			$refreshsecs = $tenth_ottl;
		}
		else
		{
			$refreshsecs = $DAY;
		}
	}
	else
	{
		if($tenth_expinterval < $DAY)
		{
			$refreshsecs = $tenth_expinterval;
		}
		else
		{
			$refreshsecs = $DAY;
		}
	}

	#
	# Save the appropriate sleep-time, but with an hour as the minimum.
	#
	if($refreshsecs >= $HOUR)
	{
		$sleeptimes{$z} = $refreshsecs;
	}
	else
	{
		$sleeptimes{$z} = $HOUR;
	}

}

#----------------------------------------------------------------------
# Routine:	compare_keys()
#
# Purpose:	Compares the contents of two keys to see if the new one ($zone,
#		$rec, and $keyin) matches a cached one previously stored (in
#		$storage->{$zone}).
#
sub compare_keys
{
	my ($storage, $zone, $rec, $keyin) = @_;
	my $newkey = 1;

	if(!exists($storage->{$zone}))
	{
		# What would nonexistence of this really mean?
	}

	for(my $i = 0; $i <= $#{$storage->{$zone}}; $i++)
	{
		# not testing: $storage->{$zone}[$i]{flags} eq $rec->flags &&

		if(($storage->{$zone}[$i]{key} eq $keyin) &&
		   ($storage->{$zone}[$i]{protocol} eq $rec->protocol) &&
		   ($storage->{$zone}[$i]{algorithm} eq $rec->algorithm))
		{
			$newkey = 0;

			# Any match is good enough, get out now.
			$i = $#{$storage->{$zone}} + 1;
		}
		else
		{
			$newkey = 1;
		}
	}

	return($newkey);
}

#----------------------------------------------------------------------
# Routine:	keys_equal()
#
# Purpose:	Computes key equality of two key references.
#		Equality is based on: key, protocol, algorithm (not flags!)
#
# Returns:	1 : equal
#		0 : not equal
#
sub keys_equal
{
	my ($key1, $key2) = @_;

	if(($key1->{key}       eq  $key2->{key})      &&
	   ($key1->{protocol}  eq  $key2->{protocol}) &&
	   ($key1->{algorithm} eq  $key2->{algorithm}))
	{
		return(1);
	}

	return(0);
}

#----------------------------------------------------------------------
# Routine:	keycmp()
#
# Purpose:	Computes key equality of to key references.
#		Equality is based on: key, protocol, algorithm, and flags.
#
#		If the key argument (the third argument) wasn't given,
#		we'll use the 'key' value from the second hash.
#
#		(Might should consider combining keycmp() with keys_equal().)
#
# Returns:	1 : equal
#		0 : not equal
#
sub keycmp
{
	my $key1 = shift;
	my $key2 = shift;
	my $key;

	if(@_ > 0)	{ $key = shift;		}
	else		{ $key = $key2->{key};	}

	if(($key1->{key}	eq  $key)		&&
	   ($key1->{flags}	eq  $key2->{flags})	&&
	   ($key1->{protocol}	eq  $key2->{protocol})	&&
	   ($key1->{algorithm}	eq  $key2->{algorithm}))
	{
		return(1);
	}

	return(0);
}

#----------------------------------------------------------------------
# Routine:	key_in_keyarr()
#
# Purpose:	Checks if a key is in a key array, returns first matching
#		location.
#		Based on: keys_equal()
#
# Returns:	<  0 : key not in key array
#		>= 0 : location of matching key in key array
#
sub key_in_keyarr
{
	my ($key, $keyarr) = @_;

	for(my $i=0; $i<$#{$keyarr}; $i++)
	{
		if(keys_equal($key, $keyarr->[$i]))
		{
			return($i);
		}
	}

	return(-1);
}

#----------------------------------------------------------------------
# Routine:	getsleeptime()
#
# Purpose:	Find the shortest sleep time of all the zones.
#		The sleep time is only used if we're doing more than
#		one pass over the keys.
#
sub getsleeptime
{
	my $slp = shift;

	return(0) if($once);

	foreach my $z (keys(%sleeptimes))
	{
		if(($slp > $sleeptimes{$z}) && ($sleeptimes{$z} > 0))
		{
			$slp = $sleeptimes{$z};
		}
	}

	return($slp);
}

#----------------------------------------------------------------------
# Routine:	getnowsecs()
#
# Purpose:	Get the seconds for the current local time.
#
sub getnowsecs
{
	my $now = localtime();
	my $nowsecs = str2time($now);

	return($nowsecs);
}

#----------------------------------------------------------------------
# Routine:	print_keyrec()
#
# Purpose:	Print a keyrec.
#
sub print_keyrec
{
	my $rec = @_[0];

	printf "flags: %s  protocol: %s  algo: %s\nkey:%s\n", $rec->{flags},
			$rec->{protocol},$rec->{algorithm}, $rec->{key};

}

#----------------------------------------------------------------------
# Routine:	daemonize()
#
# Purpose:	Run as a daemon.
#
sub daemonize
{
	chdir '/' or die "Can't chdir to /: $!";

	open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
	open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!";

	defined(my $pid = fork()) or die "Can't fork: $!";
	exit(0) if $pid;

	POSIX::setsid() or die "Can't start a new session: $!";
	umask 0;
}

#----------------------------------------------------------------------
# Routine:	mon()
#
# Purpose:	Add a line to the monitor string.
#
sub mon
{
	my ($msg) = @_;

	$monstr .= "$msg\n" if($monitor);
}

#----------------------------------------------------------------------
# Routine:	monvalue()
#
# Purpose:	Add a line to the monitor string iff it has a positive value.
#		If a plural form is provided, we'll check to see if the value
#		is one or more than one.  The appropriate form will be used.
#
sub monvalue
{
	my $val = shift;			# Value we're monitoring.
	my $msgsing = shift;			# Singular form of message.
	my $msgplural = shift;			# Plural form of message.

	return if($val == 0);

	if(($val == 1) || (! defined($msgplural)))
	{
		$monstr .= $msgsing;
	}
	else
	{
		$monstr .= $msgplural;
	}
}

#----------------------------------------------------------------------
# Routine:	vnotify()
#
# Purpose:	Send a notification, but only if the -v option was given.
#
sub vnotify
{
	my ($msg) = @_;

	notify($msg) if($opts{'v'});
}

#----------------------------------------------------------------------
# Routine:	notify()
#
# Purpose:	Mails or logs notifications, depending on configuration.
#
sub notify
{
	my ($message) = @_;

	if($opts{'L'})
	{
		openlog('trustman','pid','user') || warn "could not open syslog";
		syslog('warning',"%s", $message);
		closelog();
	}

	if($opts{'p'})
	{
		$| = 1;

		#
		# If in verbose mode, make sure messages are easily detectable
		# within the verbose output.
		#
		Verbose("v" x 70 . "\n");
		print $message;
		Verbose("^" x 70 . "\n");
	}

	if($smtpserver && $contactaddr && !$opts{'nomail'})
	{
		Verbose("  mailing $contactaddr\n");
		mailcontact(0,$smtpserver,$contactaddr,$message);
	}
}

#----------------------------------------------------------------------
# Routine:	mailcontact()
#
# Purpose:	Emails a contact address with the error output.
#
sub mailcontact
{
	my ($ok,$smtp,$contact,$msg) = @_;
	my $fromaddr = 'trustman@localhost';

	Verbose("sending mail to $contact\n");

	#
	# Set up the SMTP object and required data.
	#
	my $message = Net::SMTP->new($smtp) || die "failed to create smtp message";
	$message->mail($fromaddr);
	$message->to(split(/,\s*/,$contact));
	$message->data();

	# Create headers.
	$message->datasend("To: " . $contact . "\n");
	$message->datasend("From: " . $fromaddr . "\n");

	# Create the body of the message: the warning.
	$message->datasend("Subject: trustman notification\n\n");
	$message->datasend($msg);
	$message->datasend("\n\n");

	# Finish and send the message.
	$message->dataend();
	$message->quit;
}

#----------------------------------------------------------------------
# Routine:	Verbose()
#
# Purpose:	Prints something(s) to STDERR only if -v was specified.
#
sub Verbose
{
	print STDERR @_ if($opts{'v'});
}

#----------------------------------------------------------------------
# Routine:	Die()
#
# Purpose:	Prints a fatal error message to STDERR and exits.
#
sub Die
{
	notify(join("",@_, "\n", "This is a fatal error.  EXITING!\n"));
	print STDERR @_,"\n";
	print STDERR "This is a fatal error.  EXITING!\n";
	exit(5);
}

#----------------------------------------------------------------------
# Routine:	usage()
#
# Purpose:	Prints a command-usage message, with an optional error
#		message, and exits.
#
sub usage
{
	my $rc = shift;					# Exit code.
	my ($extratext) = @_;

	print STDERR "\nError:\n  $extratext\n\n" if($extratext);

	print STDERR "trustman [-k /PATH/TO/DNSVAL.CONF] [-n /PATH/TO/NAMED.CONF] [-z ZONE] [-L] [-f]\n         [-S] [-c OUTCONFIGFILE] [-d DNSSECCONFIGFILE] [-v] [-V]

	File Options:
		-anchor_data_file FILE (-a)
		-config FILE (-c)
		-dtconfig DTCONFFILE (-d)
		-dnsval_conf_file /PATH/TO/DNSVAL.CONF (-k)
		-named_conf_file /PATH/TO/NAMED.CONF (-n)
		-resolv_conf_file CONFFILE (-r)
		-root_hints_file /PATH/TO/root.hints (-o)
		-tmp_dir TMPDIR (-T)

	Logging and Output Options:
		-mail_contact_addr EMAIL_ADDRESS (-m)
		-smtp_server SMTPSERVERNAME (-s)
		-no_error (-N)
		-print (-p)
		-syslog (-L)
		-nomail

	Operational Options:
		-zone ZONE (-z)
		-hold_time SECONDS (-w)
		-single_run (-S)
		-foreground (-f)
		-sleeptime SECONDS (-t)
		-monitor (-M)

	Testing Options:
		-norevoke

	Help Options:
		-help (-h)
		-verbose (-v)
		-version (-V)

	Extra Notes:
		- If a zone is not specified, all zones in the
		  key_containing_files will be checked.

		- If missing options are not specified on the command
		  line, some values will be read from the dnssec-tools.conf.
		  Run with the -c flag to generate suitable dnssec-tools.conf
		  configuration lines.
";

	exit($rc);
}

#----------------------------------------------------------------------
# Routine:	show_version()
#
# Purpose:	Prints a version message and exits.
#
sub show_version
{
	print STDERR "$VERS\n";
	print STDERR "$DTVERS\n";
	exit(0);
}

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

=pod

=head1 NAME

trustman - Manage keys used as trust anchors

=head1 SYNOPSIS

trustman [options]

=head1 DESCRIPTION

B<trustman> manages keys used by DNSSEC as trust anchors in compliance with
RFC5011.  It may be used as a daemon for ongoing key verification or manually
for initialization and one-time key verification.

By default, B<trustman> runs as a daemon to ensure that keys stored locally in
configuration files still match the same keys fetched from the zone where they
are defined.  In addition, these checks can be run once manually (B<-S>) and
in the foreground (B<-f>).

For each key mismatch check, if key mismatches are detected then B<trustman>
performs the following operations:

    - sets an add hold-down timer for new keys;
    - sets a remove hold-down timer for missing keys;
    - removes revoked keys from the configuration file.

On subsequent runs, the timers are checked.  If the timers have expired, keys
are added to or removed from the configuration file, as appropriate.

B<named.conf> and B<dnsval.conf> are the usual configuration files.  These
files must be specified in the DNSSEC-Tools configuration file or in command
line options.

=head1 OPTIONS

B<trustman> takes a number of options, each of which is described in this
section.  Each option name may be shortened to the minimum number of unique
characters, but some options also have an alias (as noted.)  The single-letter
form of each option is denoted in parentheses, e.g.: B<-anchor_data_file>
(B<-a>).

=over 4

=item B<-anchor_data_file file (-a)>

A persistent data file for storing new keys waiting to be added.

=item B<-config file (-c) >

Create a configuration file for B<trustman> from the command line options
given.  The existing DNSSEC-Tools configuration file is copied to the
specified configuration file, and new configuration entries are appended
corresponding to the command line options.  B<trustman>-specific entries
already in the existing configuration file will be replaced with new entries
from the command line.  This will allow fewer command line options to be
specified in the future.

=item B<-dnsval_conf_file /path/to/dnsval.conf (-k)>

A B<dnsval.conf> file to read, and possibly be updated.

=item B<-dtconfig config_file (-d)>

Name of an alternate DNSSEC-Tools configuration file to be processed.
If specified, this configuration file is used I<in place> of the normal
DNSSEC-Tools configuration file, B<not> in addition to it.  Also, it will be
handled prior to I<keyrec> files, I<rollrec> files, and command-line options.

=item B<-foreground (-f)>

Run in the foreground.  B<trustman> will still run in a loop.
To run once, use the B<-single_run> option instead.

=item B<-hold_time seconds (-w)>

The value of the hold-down timer.  This is the number of seconds from the time
that a new key is found.  Generally, the default and recommended value of 30
days should be used.

=item B<-mail_contact_addr email_address (-m)>

Mail address for the contact person to whom reports should be sent.

=item B<-monitor (-M)>

Indicates that B<trustman> was run from a monitoring system, and a summary
of events will be printed.  Specifying this option automatically turns on
the B<-single_run> option and turns off the B<-verbose> option.

This was developed for use with the Nagios monitoring system, but it can
be adapted for other monitors.

=item B<-named_conf_file /path/to/named.conf (-n)>

A B<named.conf> file to read, and possibly update.

=item B<-nomail>

Prevents mail from being sent, even if an SMTP server was specified in the
configuration file.  This is useful for only sending notifications via
B<stdout> (B<-p>) or B<syslog> (B<-L>).

=item B<-norevoke>

This option turns off checks for the REVOKE bit.

=item B<-no_error (-N)>

Send report even when there are no errors.

=item B<-print (-p)>

Log messages to B<stdout>.

=item B<-resolv_conf_file conffile (-r)>

A B<resolv.conf> file to read.  B</dev/null> can be specified to force
I<libval> to recursively answer the query rather than asking other name
servers.)

=item B<-root_hints_file /path/to/root.hints (-o)>

A B<root.hints> file to read.

=item B<-single_run (-S)>

Do not loop, but run only once.

=item B<-sleeptime seconds (-t)>

The number of seconds to sleep between checks.  Default is 3600 (one hour.)

=item B<-smtp_server smtpservername (-s)>

The SMTP server that B<trustman> should use to send reports by mail.

=item B<-syslog (-L)>

Log messages to B<syslog>.

=item B<-tmp_dir directory (-T)>

Specifies where temporary files should be created.  This is used when
creating new versions of the B<dnsval.conf> and B<named.conf> files before
they are moved into place.

Files created in this directory will be B<renamed> to their final location.
You should ensure that this directory, the final B<dnsval.conf> location,
and the final B<named.conf> location are on the same disk partition.
Most operating systems will only rename files within a partition and will 
give an error if told to rename a file from one partition to another.

=item B<-zone zone (-z)>

The zone to check.  Specifying this option supersedes the default
configuration file.

=item B<-help (-h)>

Display a help message.

=item B<-verbose (-v)>

Gives verbose output.

=item B<-Version (-V)>

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

=back

=head1 CONFIGURATION

In addition to the command line arguments, the B<dnssec-tools.conf> file can
be configured with the following values to remove the need to use some of
the command-line options.  The command-line options always override the
settings in the B<dnssec-tools.conf> file.

=over 4

=item B<taanchorfile file>

This specifies the file where B<trustman> state information will be kept.
This is equivalent to the B<-anchor_data_file> flag.

=item B<tacontact contact_email>

This is equivalent to the B<-mail_contact_addr> flag for specifying to whom
email notices will be sent.

=item B<tadnsvalconffile file>

This specifies the B<dnsval.conf> file to read and write.
This is equivalent to the B<-dnsval_conf_file> flag.

=item B<tanamedconffile file>

This specifies the B<named.conf> file to read and write.
This is equivalent to the B<-named_conf_file> flag.

=item B<taresolvconffile file>

This specifies the B<resolv.conf> file to use.
This is equivalent to the B<-resolv_conf_file> flag.

=item B<taroothintsfile file>

This specifies the B<root.hints> file to read.
This is equivalent to the B<-root_hints_file> flag.

=item B<tasmtpserver servername>

This is equivalent to the B<-smtp_server> flag for specifying the SMTP server
to which email notices will be sent.

=item B<tatmpdir directory>

This specifies where temporary files should be created.  This is used when
creating new versions of the B<dnsval.conf> and B<named.conf> files before
they're moved into place.

See the note about renaming in the description of the B<-tmp_dir> option.

=back

=head1 EXIT CODES

B<trustman> may exit for the following reasons:

    0 - Successful execution.  In daemon mode, this may just mean
	that the daemon was successfully started.  The daemon itself
	may exit with some other error.

    1 - Invalid options were specified.

    2 - No new-key file was specified.

    3 - Unable to open the new-key file.

    4 - Unable to determine a set of zones to check.

    5 - Some form of file-management error was encountered.

=head1 COPYRIGHT

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

=head1 Author

Lindy Foster

(Current contact for B<trustman> is Wayne Morrison, tewok@tislabs.com.)

=head1 SEE ALSO

B<Net::DNS::SEC::Tools::conf.pm(3)>,
B<Net::DNS::SEC::Tools::defaults.pm(3)>,

B<dnssec-tools.conf(5)>

=cut
