#!/usr/bin/perl
#
# Copyright 2012-2014 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
# owl-dnstimer						Owl Monitoring System
#
#	This script runs timing tests for DNS lookups.  It starts a query
#	entity (thread or subprocess) for each of a set of queries defined in
#	the Owl configuration file.  Each thread periodically sends a DNS
#	request for a specified host to a particular nameserver.  The time
#	taken to fulfill the request is recorded for later transfer to the Owl
#	manager.
#
# Revision History:
#	1.0	Initial version.				121201
#
#	2.0	Released as part of DNSSEC-Tools 2.0.		130301
#	2.0.1	Modified to allow multiple types of queries.	130319
#	2.0.2	Added -nolog and owl_log() calls.		130530
#	2.0.3	Added the -nodnssec option.			130617
#	2.0.4	Moved data to <data/dns> subdirectory.		130726
#	2.0.5	Shutdown on SIGUSR1.				140806
#

use strict;

#
# Version information.
#
my $NAME   = "owl-dnstimer";
my $VERS   = "$NAME version: 2.0.5";
my $DTVERS = "DNSSEC-Tools version: 2.0";

use FindBin;

use lib "$FindBin::Bin/../perllib";
use owlutils;

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

use Net::DNS;
use Net::DNS::Packet;
use Net::DNS::Resolver;
# use Net::DNS::SEC::Validator;
use Net::hostent;
use Socket;

#
# Before including the threads module, we'll make sure we're able to.
# If we can, we'll be happy campers.
# If we can't, we'll continue and be forky instead of thready.
#
my $runthreaded;				# Flag for running threaded.
if(eval { require threads } == 1)
{
	require threads;
	$runthreaded = 1;
}
else
{
	$runthreaded = 0;
}

use threads::shared;

use Log::Dispatch;
use Log::Dispatch::FileRotate;

use Date::Format;
use POSIX qw(setsid SIGUSR1 SIGUSR2);
use Time::HiRes qw(time);

#------------------------------------------------------------------------
# Defaults and some constants.

my $NSBASE = 'root-servers.net';		# Base name for root servers.

my $DEF_CONFIG	= $owlutils::DEF_CONFIG;	# Default config file nodename.
my $DEF_CONFDIR	= $owlutils::DEF_CONFDIR;	# Default config directory.
my $DEF_DATADIR	= $owlutils::DEF_DATADIR;	# Default data directory.
my $DEF_LOGDIR	= $owlutils::DEF_LOGDIR;	# Default log directory.

my $PIDFILE	= "$NAME.pid";			# Filename of process-id file.

my $FILEEXT	= "dns";		# File extension for data files and
					# name of sensor's data subdirectory.

#------------------------------------------------------------------------

#
# Data required for command line options.
#
my %options = ();			# Filled option array.
my @opts =
(
	'confdir|cd=s',				# Specify config directory.
	'config|cf=s',				# Specify config file.
	'datadir=s',				# Specify data directory.
	'logdir=s',				# Specify log directory.

	'defaults',				# Print defaults.
	'foreground|fg',			# Run in foreground.
	'nodnssec',				# Don't use DNSSEC resolution.
	'nolog',				# Generate no logging output.
	'querylog',				# Generate query-log entries.
	'stop',					# Stop execution.

	'help',					# Give help message.
	'Version',				# Give version info.
	'verbose',				# Give verbose output.
);

my $verbose = 0;
my $confdir;					# Config directory.
my $config;					# Config file.
my $datadir;					# Data directory.
my $foreground;					# Foreground-execution flag.
my $logdir;					# Log directory.
my $logfile;					# Log file.
my $nodnssec = 0;				# Don't-use-DNSSEC flag.
my $nolog = 0;					# No-logging flag.
my $querylog = 0;				# Query-logging flag.
my $stopper;					# Stop-execution flag.

#------------------------------------------------------------------------
#
# Error strings for log messages.  This is indexed by a code set in nstimer().
# (Actually, it isn't used right now.)
#
my @errors =
(
	'NOERROR',		# 0
	'FORMERR',		# 1
	'SERVFAIL',		# 2
	'NXDOMAIN',		# 3
	'NOTIMP',		# 4
	'REFUSED',		# 5
	'YXDOMAIN',		# 6
	'YXRRSET',		# 7
	'NXRRSET',		# 8
	'NOTAUTH',		# 9
	'NOTZONE',		# 10
	'UNASSIGNED',		# 11
	'UNASSIGNED',		# 12
	'UNASSIGNED',		# 13
	'UNASSIGNED',		# 14
	'UNASSIGNED',		# 15
	'BADVERS/BADSIG',	# 16
	'BADKEY',		# 17
	'BADTIME',		# 18
	'BADMODE',		# 19
	'BADNAME',		# 20
	'BADALG',		# 21
	'BADTRUNC',		# 22
);

my $MAXERR = 22;				# Maximum error.

#------------------------------------------------------------------------
#
# Constants for configuration data.
#

my $idletime = 1 * 60;			# Main thread wait time.

#------------------------------------------------------------------------
# Data and log message data.
#

my $slog;				# Sensor's log object.
my %loginfo = ();			# Logging information.

my $nextroll = 0;			# Time of next datafile rollover.

my $pidfile;				# Name of process-id file.
my $sensorname;				# Sensor's name from config file.

#------------------------------------------------------------------------
# Threads and queries.
#

our @cf_owlqueries = ();			# List of targets.
our @cf_targets	   = ();			# List of targets.
our @cf_servers	   = ();			# List of nameservers.
our @cf_qargs	   = ();			# List of query arguments.
our @cf_intervals  = ();			# List of query intervals.
our @cf_timeouts   = ();			# List of query timeouts.
our @cf_rollints   = ();			# Datafile rollover interval.
our @cf_states	   = ();			# State of targets.

my @seekers = ();				# Query entity objects.
my @resolvers = ();				# Resolver objects.
my @datafiles = ();				# Datafiles for queries.
my @datafds = ();				# Descriptors for datafiles.

#------------------------------------------------------------------------

main();
exit(0);

#------------------------------------------------------------------------
# Routine:	main()
#
sub main
{
	my $nscount = 0;		# Number of query entities started.

	#
	# Check our options and read the configuration file.
	#
	doopts();
	owl_setup($NAME,$confdir,$datadir,$logdir);
	if(owl_readconfig($config,$datadir,$logdir) != 0)
	{
		exit(2);
	}

	#
	# Perform initialization steps.
	#
	startup();

	exit(1) if(owl_chkdir('data', $datadir) == 1);
	exit(1) if((! $nolog) && (owl_chkdir('log', $logdir) == 1));

	#
	# Daemonize ourself.
	# 
	if((! $foreground) && fork())
	{
		print "$NAME started and running as daemon\n";
		exit(0);
	}
	POSIX::setsid() if(! $foreground);
	owl_writepid();

	#
	# Build a name resolver object for each target/query.
	#
	makeresolvers();

	#
	# Print the nameservers we're using.
	#
	printnss();

	#
	# Start a query entity for each nameserver that has a valid entry.
	#
	owl_log($slog,1,"creating queries for each target/nameserver pair");
	print STDERR "master starting query-entities\n" if($verbose);
	for(my $ind=0; $ind < @cf_targets; $ind++)
	{
		$datafds[$ind] = -1;

		next if(($cf_owlqueries[$ind] ne 'dnstimer') ||
			($cf_states[$ind] == 0));

		push @seekers, forge($ind);

		$nscount++;
	}

	#
	# Complain and exit if we didn't start any query entities.
	#
	if($nscount == 0)
	{
		owl_log($slog,1,"no nameserver query entities started");
		print STDERR "no nameserver query entities started\n";
		exit(4);
	}

	#
	# Master will just sit here twiddling its thumbs.
	#
	while(42)
	{
		select(undef,undef,undef,$idletime);
	}

}

#------------------------------------------------------------------------
# Routine:	doopts()
#
sub doopts
{
	#
	# Parse the options.
	#
	GetOptions(\%options,@opts) || usage();

	#
	# Handle a few immediate flags.
	#
	version()	if(defined($options{'Version'}));
	usage(1)	if(defined($options{'help'}));
	owl_printdefs()	if(defined($options{'defaults'}));

	#
	# Set our option variables based on the parsed options.
	#
	$confdir    = $options{'confdir'}    || $DEF_CONFDIR;
	$config	    = $options{'config'}     || $DEF_CONFIG;
	$foreground = $options{'foreground'} || 0;
	$nodnssec   = $options{'nodnssec'}   || 0;
	$nolog	    = $options{'nolog'}	     || 0;
	$querylog   = $options{'querylog'}   || 0;
	$stopper    = $options{'stop'}       || 0;
	$verbose    = $options{'verbose'};

	$datadir    = $options{'datadir'};
	$logdir	    = $options{'logdir'};

	#
	# Moosh together a few variables to build the config file.
	#
	$config = "$confdir/$config" if($config !~ /\//);

	#
	# Set the logging flag in the shared module.
	#
	$owlutils::logflag = ! $nolog;

	#
	# TEMPORARY:	Don't allow Validator use for now.  Validator
	#		seems to have problems on Linux when using
	#		multiple validator objects.
	#
	if(! $nodnssec)
	{
		print STDERR "\n";
		print STDERR "\n";
		print STDERR "Net::DNS::SEC::Validator is not currently supported; using Net::DNS::Resolver in the interim\n";
		print STDERR "\n";
		print STDERR "\n";

		$nodnssec = 1;
	}

	#
	# We'll only allow query logging if logging is also to be allowed.
	#
	if($nolog)
	{
		$querylog = 0;
	}
}

#------------------------------------------------------------------------
# Routine:	startup()
#
sub startup
{
	#
	# Set up our signal handlers.
	#
	sigurd();

	#
	# Set up our log file.
	#
	$slog = owl_setlog($NAME) if((! $nolog) && (! $stopper));

	#
	# Shutdown any owl-dnstimer instances that are running.
	#
	if($stopper)
	{
		owl_halt('owl-dnstimer');
		exit(0);
	}

	#
	# Make sure we're the only owl-dnstimer running.  We'll also allow a
	# user to signal the other owl-dnstimer to shut down.
	#
	if(owl_singleton(0) == 0)
	{
		#
		# If we're not the only owl-dnstimer running, we'll
		# complain and exit.
		#
		owl_log($slog,1,"$NAME already running");
		print STDERR "$NAME already running\n";
		exit(2);
	}

	#
	# Get some data set in our utilities.
	#
	$pidfile    = $owlutils::pidfile;
	$datadir    = $owlutils::datadir;
	$logdir	    = $owlutils::logdir;
	$sensorname = $owlutils::hostname;

	@cf_owlqueries = @owlutils::cf_owlqueries;
	@cf_targets    = @owlutils::cf_targets;
	@cf_servers    = @owlutils::cf_servers;
	@cf_qargs      = @owlutils::cf_qargs;
	@cf_intervals  = @owlutils::cf_intervals;
	@cf_timeouts   = @owlutils::cf_timeouts;
	@cf_rollints   = @owlutils::cf_rollints;
	@cf_states     = @owlutils::cf_states;

	#
	# Check that the top-level data directory exists, then set
	# the name for the sensor module's data subdirectory.
	#
	exit(1) if(owl_chkdir('data', $datadir) == 1);
	$datadir .= "/$FILEEXT";

	#
	# Give some extra-tasty output.
	#
	if($verbose)
	{
		print "confdir - $confdir\n";
		print "config  - $config\n";
		print "datadir - $datadir\n";
		print "logdir  - $logdir\n";
		print "\n";
	}

}

#------------------------------------------------------------------------
# Routine:	makeresolvers()
#
# Purpose:	Build a resolver object for each defined nameserver.
#		These objects will be saved in the @resolvers array.
#
sub makeresolvers
{
	my $mrez = undef;				# Master resolver.

	for(my $ind=0; $ind < @cf_servers; $ind++)
	{
		my $nsname;				# Name server's name.
		my $res;				# Resolver object.
		my $timeout;				# Query timeout value.

		#
		# Skip entries that aren't ours.
		#
		next if($cf_owlqueries[$ind] ne 'dnstimer');

		$nsname  = $cf_servers[$ind];
		$timeout = $cf_timeouts[$ind];

		#
		# Build the resolver object.
		#
		if($nodnssec)
		{
			$res = Net::DNS::Resolver->new(
						  nameservers => [$nsname],
						  recurse     => 0,
						  retry       => 1,
						      );

			#
			# Set some resolver fields.
			#
			$res->cdflag(1);
			$res->tcp_timeout($timeout);
			$res->udp_timeout($timeout);
		}
		else
		{
			my $hent;		# Name server's host entry.
			my $host;		# Name server's text address.
			my $nsaddr;		# Name server's real address.

			#
			# Get the resolver from which other resolvers will
			# be found.
			#
			if($mrez == undef)
			{
				$mrez = new Net::DNS::SEC::Validator();
			}

			#
			# Ge the host entry for this name server.
			#
			$hent = $mrez->gethostbyname($nsname);
			if(! defined($hent))
			{
				print "undefined name server $nsname\n";
				next;
			}

			#
			# Get the name server's address from the host entry.
			#
			$host = shift @{$hent->addr_list};
			$nsaddr = inet_ntoa($host);

			#
			# Finally, we'll get the resolver for this name server.
			#
			$res = undef;
			$res = new Net::DNS::SEC::Validator(nslist => $nsaddr);
		}

		#
		# Save this nameserver's resolver object.
		#
		push @resolvers, $res;
	}

}

#------------------------------------------------------------------------
# Routine:	forge()
#
# Purpose:	This routine creates a new query entity.  If the system and
#		Perl support threads, we'll create a new thread.  If threads
#		aren't supported, we'll create a new child process.
#
#		Most calls will be to start a DNS-query entity, and the
#		argument is the array index for query information.  If
#		the argument is -1, then a special entity will be created
#		to handle logging.
#
sub forge
{
	my $qind = shift;				# Query's array index.
	my $ret;					# Return value.

	if($runthreaded)
	{
		#
		# We're running threaded, so we'll create a thread for
		# queries or logging.
		#
		$ret = threads->create(\&questor,$qind);
	}
	else
	{
		#
		# We're not running threaded, so we'll create a process
		# for queries.
		#
		if(($ret=fork()) == 0)
		{
			#
			# We'll set up for clean exits at shutdown.
			#
			$SIG{USR1} = \&sigend;

			#
			# Do the Right Thing depending on the index --
			# logging for negative values, query thread for
			# other values.
			#
			owl_log($slog,1,"starting query-proc ($$) for $cf_targets[$qind]/$cf_servers[$qind]/$cf_qargs[$qind]") if($verbose);
			print STDERR "starting query-proc (pid $$) for $cf_targets[$qind]/$cf_servers[$qind]/$cf_qargs[$qind]\n" if($verbose);

			questor($qind);

			#
			# We should never get here, but just in case...
			#
			exit(0);
		}
	}

	#
	# The master entity will return either the thread object or the
	# process id.
	#
	return($ret);
}

#------------------------------------------------------------------------
# Routine:	questor()
#
# Purpose:	This routine runs the nameserver timing queries.  It is run
#		in a separate entity (thread or process) for each target/
#		nameserver/query-type triplet.
#
sub questor
{
	my $qind = shift;				# Query's array index.
	my $target;					# Query's target host.
	my $ns;						# Query's name server.
	my $dfn = '';					# Datafile name.

	$target = $cf_targets[$qind];
	$ns = $cf_servers[$qind];

	while(42)
	{
		my $now;				# Current time.
		my $later;				# Next time to run.
		my $naptime;				# Time to sleep.

		#
		# Get the name of the current data file and save it in the
		# @datafiles array.  We may roll the data file.
		#
		getdatafile($qind,$ns);

		#
		# Give some verbose output about the change in datafile.
		#
		if($verbose && ($dfn ne $datafiles[$qind]))
		{
			print STDERR "new datafile - $datafiles[$qind]\n";
			owl_log($slog,1,"new datafile - $datafiles[$qind]");
			$dfn = $datafiles[$qind];
		}

		#
		# Get the time we're supposed to check again.
		#
		$later = time() + $cf_intervals[$qind];

		#
		# Run our timing check of the nameserver.
		#
		nstimer($qind);

		#
		# Calculate how much time we need to sleep, based on
		# how much time we spent in the ns timing check.
		#
		$now = time();
		$naptime = sprintf("%1.0f", ($later - $now));

		#
		# And let's wait for our next checkup time.
		#
		sleep($naptime);
	}
}

#------------------------------------------------------------------------
# Routine:	getdatafile()
#
sub getdatafile
{
	my $qind = shift;	# Query index.
	my $ns;			# Nameserver on whose behalf we're working.
	my $tg;			# Target we're querying.
	my $qt;			# Query type.
	my $ctime = time();	# Current time.
	my @cronos;		# Current GMT time, broken out into atoms.
	my $datafn;		# Full data path to new data file.
	my $datafd;		# File descriptor to new data file.

	#
	# Use the current datafile if the next rollover time is yet to come.
	#
	return if($nextroll > $ctime);

	#
	# Build the filename for the next datafile.
	#
	$ns = $cf_servers[$qind];
	$tg = $cf_targets[$qind];
	$qt = $cf_qargs[$qind];
	@cronos = gmtime($ctime);
	$datafiles[$qind] = sprintf("%02d%02d%02d.%02d%02d,$sensorname,$tg,$ns,$qt.$FILEEXT",
						$cronos[5] % 100,
						$cronos[4] + 1,
						$cronos[3],
						$cronos[2],
						$cronos[1]);

	#
	# Create a new data file.
	#
	$datafn = "$datadir/$datafiles[$qind]";
	if(($datafd=new IO::File "> $datafn") == 0)
	{
		owl_log($slog,0,"$tg:$ns:  unable to open $datafn : $!");

		$seekers[$qind]->join() if($runthreaded);
	}

	#
	# Close the old data file.
	#
	if($datafds[$qind] != -1)
	{
		my $dfd;				# Datafile descriptor.

		$dfd = $datafds[$qind];
		$dfd = $$dfd;

		$dfd->close();
	}

	#
	# Save the new file descriptor.
	#
	autoflush $datafd, 1;
	$datafds[$qind] = \$datafd;
	owl_log($slog,0,"$tg:$ns:  changing to new datafile $datafn");

	#
	# Calculate the time for the next datafile rollover.
	#
	$nextroll = $ctime + $cf_rollints[$qind];

}

#------------------------------------------------------------------------
# Routine:	nstimer()
#
# Purpose:	Send a message to a nameserver and time how long it takes
#		for a response to arrive.  We'll then log the message and
#		response time for use by a monitor.  This is timing how
#		long it takes a nameserver to respond to a request.
#
sub nstimer
{
	my $qind = shift;				# Query index.
	my $target;					# Target host.
	my $ns;						# Name server.
	my $resolver;					# Resolver object.
	my $resp;					# Resolver's response.
	my $reply = 'undefined response';		# Reply message.
	my $rcode = -1;					# send() error code.

	my $qtype;					# Query type.
	my $family = 'IN';				# Address family.

	my $time0;					# Start time.
	my $time1;					# Stop time.
	my $timediff;					# Time difference.

	#
	# Get the some objects.
	#
	$ns	  = $cf_servers[$qind];
	$target   = $cf_targets[$qind];
	$qtype	  = $cf_qargs[$qind];
	$resolver = $resolvers[$qind];

	#
	# Make sure we were given a nameserver.
	#
	if(!defined($ns))
	{
		owl_log($slog,1,"nstimer:  undefined nameserver");
		print STDERR "nstimer:  undefined nameserver\n";
		return;
	}

	#
	# If we want an anycast result, we'll have to adjust some fields.
	#
	if($qtype =~ /^anycast$/i)
	{
		$target	= 'hostname.bind';
		$qtype	= 'TXT';
		$family	= 'CH';
	}

	#
	# Send the query to the nameserver, and time the query.
	#
	if($nodnssec)
	{
		$time0 = time();
		$resp = $resolver->send($target, $qtype, $family);
		$time1 = time();
	}
	else
	{
		my $err;

		$time0 = time();
		$resp = $resolver->res_query($target, $family, $qtype);
		$time1 = time();
		($resp,$err) = new Net::DNS::Packet(\$resp);
	}

	#
	# Calculate the time the query took.
	#
	$timediff = $time1 - $time0;

	#
	# We'll try to make sense of the answer, if one was returned.
	#
	if(defined($resp))
	{
		$rcode = $resp->header->rcode;

		#
		# If there's an answer, we'll stuff the text response
		# into the reply string.
		#
		if(($resp->header->ancount > 0) && defined($resp->answer))
		{
			foreach my $txtrr ($resp->answer)
			{
				$reply = $txtrr->string;
			}
		}
	}

	#
	# Send the answer off to be logged.
	#
	savedatum($qind,$reply,$rcode,$time0,$timediff);
}

#------------------------------------------------------------------------
# Routine:	savedatum()
#
# Purpose:	Save a data point -- found in nstimer() -- to our data file.
#
sub savedatum
{
	my $qind = shift;			# Query index.
	my $reply = shift;			# Nameserver's reply.
	my $rcode = shift;			# Success value of query.
	my $cronos = shift;			# Time test was run.
	my $td = shift;				# Time to run test.

	my $qtype;				# Query type.
	my $succstr;				# Success code string.
	my $anycast = '';			# Nameserver's anycast instance.
	my $msg;				# Formatted message to send.
	my $dfd;				# Datafile descriptor.

	#
	# Get some data -- local variable for quick-access.
	#
	$qtype = $cf_qargs[$qind];

	#
	# If the anycast request was successful, we'll get the anycast
	# server's name.
	#
	if(($qtype eq 'ANYCAST') && ($rcode eq 'NOERROR'))
	{
		$reply =~ /"(.*)"/;
		$anycast = $1;
	}

	#
	# Get the success string for the query.
	#
	$succstr = $rcode;

	$msg = sprintf("%10.5f $cf_targets[$qind] $cf_servers[$qind] $qtype %10.15f $succstr $anycast",$cronos,$td);


	#
	# Maybe send the message to our log file.
	#
	owl_log($slog,0,$msg) if($querylog);

	$dfd = $datafds[$qind];
	$dfd = $$dfd;

	print $dfd "$msg\n";

}

#------------------------------------------------------------------------
# Routine:	sigurd()
#
# Purpose:	Set up signal handlers.
#
sub sigurd
{
	$SIG{HUP}  = \&cleanup;
	$SIG{INT}  = \&cleanup;
	$SIG{QUIT} = \&cleanup;
	$SIG{TERM} = \&cleanup;
	$SIG{USR1} = \&cleanup;
	$SIG{USR2} = \&sigmund;

}

#------------------------------------------------------------------------
# Routine:	sigmund()
#
# Purpose:	Dummy signal handler for SIGUSR2.
#
sub sigmund
{
	print "$NAME:  use \"$NAME -stop\" to shutdown\n";
}

#------------------------------------------------------------------------
# Routine:	sigend()
#
# Purpose:	Signal handler for SIGUSR2 for query subprocs.  This is
#		only used if we're unthreaded.
#
sub sigend
{
	exit(0);
}

#------------------------------------------------------------------------
# Routine:	cleanup()
#
# Purpose:	Close down query entities.
#
sub cleanup
{

	if($runthreaded)
	{
		#
		# Shut down the query threads.
		#
		owl_log($slog,1,"shutting down query-threads");
		for(my $qind=0; $qind < @seekers; $qind++)
		{
			my $qt = $seekers[$qind];
			my $tid = $qt->tid(); 

			owl_log($slog,1,"shutting down query-entity for $cf_targets[$qind]/$cf_servers[$qind]") if($verbose);
			$qt->kill('SIGUSR2')->detach();
		}
	}
	else
	{
		#
		# Shut down the query subprocesses.
		#
		owl_log($slog,1,"shutting down query-procs");
		for(my $qind=0; $qind < @seekers; $qind++)
		{
			my $qp = $seekers[$qind];

			owl_log($slog,1,"shutting down query-proc for $cf_targets[$qind]/$cf_servers[$qind]") if($verbose);
			kill(SIGUSR1, $qp);
		}

	}


	#
	# Remove the process-id file.
	#
	owl_log($slog,1,"unlinking pidfile $pidfile") if($verbose);
	print STDERR "\n\nunlinking pidfile \"$pidfile\"\n" if($verbose);
	unlink($pidfile);

	#
	# Wait a moment for the final log message to be written.
	#
	owl_log($slog,1,"shutting down...");
	print STDERR "$NAME shutting down...\n" if($verbose);
	sleep(1);
	exit(0);
}

#------------------------------------------------------------------------
# Routine:	printnss()
#
# Purpose:	Print config info for each defined target/nameserver pair.
#
sub printnss
{
	return if(!$verbose);

	for(my $qind=0; $qind < @cf_servers; $qind++)
	{
		my $msg;

		#
		# Skip entries that aren't ours.
		#
		next if($cf_owlqueries[$qind] ne 'dnstimer');

		$msg = sprintf("%10s/%10s:  interval %d, timeout %d, state %s, rollint %d",
				$cf_targets[$qind],
				$cf_servers[$qind],
				$cf_intervals[$qind],
				$cf_timeouts[$qind],
				$cf_states[$qind] ? 'active' : 'inactive',
				$cf_rollints[$qind]);

		owl_log($slog,1,"$msg");
	}
}

#----------------------------------------------------------------------
# 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:  $0 [options]\n";

	print STDERR "\t\where options are:\n";
	print STDERR "\t\t-confdir config-directory\n";
	print STDERR "\t\t-cd config-directory\n";
	print STDERR "\t\t-config config-file\n";
	print STDERR "\t\t-cf config-file\n";
	print STDERR "\t\t-datadir data-directory\n";
	print STDERR "\t\t-defaults\n";
	print STDERR "\t\t-foreground\n";
	print STDERR "\t\t-fg\n";
	print STDERR "\t\t-logdir log-directory\n";
	print STDERR "\t\t-nodnssec\n";
	print STDERR "\t\t-nolog\n";
	print STDERR "\t\t-querylog\n";
	print STDERR "\t\t-stop\n";
	print STDERR "\t\t-help\n";
	print STDERR "\t\t-verbose\n";
	print STDERR "\t\t-Version\n";


	exit(0);
}

#--------------------------------------------------------------------------

=pod

=head1 NAME

owl-dnstimer - DNS query timer for the Owl Monitoring System

=head1 SYNOPSIS

  owl-dnstimer [options] <config file>

=head1 DESCRIPTION

B<owl-dnstimer> runs timing tests of DNS lookups.  It starts a set of threads,
each of which periodically sends a DNS query for a specified target host to a
particular nameserver.  The DNS query type, target, and nameserver are defined
in the Owl configuration file.  The time taken from query to response is saved
in a file specifically for that nameserver, target, and query type.  The data
so collected are intended to be used in the Owl system to track the
responsiveness of DNS nameservers.

The B<owl-dnstimer> configuration file defines which nameservers are contacted,
as well as how often they are contacted.  B<owl-dnstimer> maintains a log file
that tracks the status of requests and query status.  B<owl-dnstimer>'s
configuration and data files, as well as the layout for the environment, are
described in their own man pages.

B<owl-dnstimer> will run threaded if the operating system or the installed
version of Perl allow.  In this case, each query will be handled by its own
thread.  If threaded execution is not allowed, then each query will be
handled by its own process.

=head2 FILE ORGANIZATION

B<owl-dnstimer> assumes that the file hierarchy for the sensor is arranged like
this:

    bin/owl-dnstimer
    conf/owl.conf
    data/<data files>
    log/<log files>
    perllib/owlutils.pm

These directories may be in the home directory of the executing user,
or they may be located in another directory elsewhere.  However, the four
directories should be kept together.

Several options are available that alter this behavior.  They allow you to
specify the location of the B<conf>, B<data>, and B<log> directories.  If
these options are used, then this default file hierarchy need not be followed.

=head1 OPTIONS

=over 4

=item B<-confdir config-directory>

=item B<-cd config-directory>

Specifies the directory that holds the Owl configuration file.  If this is
not given, then the default B<conf> name will be used.  If this is a relative
path, it will be relative from the point of execution.

The B<owl-dnstimer.pid> file is also stored in this directory.

=item B<-config config-file>

=item B<-cf config-file>

Specifies the Owl configuration file.  If I<config-file> does not contain
any directory nodes, then the specified name will be prefixed with the
configuration directory.  If it does contain directory nodes, the
configuration directory (default or option-specified) will be ignored.
If this is not given, then the default B<owl.conf> name will be used.

=item B<-datadir data-directory>

Specifies the directory that will hold the B<owl-dnstimer> data files.  If
this is not given, then the default B<data> name will be used.  If this is
a relative path, it will be relative from the point of execution.  If this
directory doesn't exist, it will be created.

=item B<-defaults>

Print the query default values for B<owl-dnstimer> and exit.  These are the
program defaults, not the configuration- and option-enhanced values.

=item B<-foreground>

=item B<-fg>

B<owl-dnstimer> will run as a foreground process if either of these options is
given.  Otherwise, it will run as a daemon.

=item B<-logdir log-directory>

Specifies the directory that will hold the B<owl-dnstimer> log files.
If this is not given, then the default B<log> name will be used.  If this
is a relative path, it will be relative from the point of execution.  If
this directory doesn't exist, it will be created.

=item B<-nodnssec>

By default, B<owl-dnstimer> resolves names with the DNSSEC-Tools
B<Net::DNS::SEC::Validator> module.  If this option is given, then
the B<Net::DNS::Resolver> module will be used instead.

=item B<-nolog>

Do not write to the log file.

=item B<-querylog>

Write query results to the log file.  This is only allowed if B<-nolog>
is not specified.

=item B<-stop>

Stops the execution of an existing B<owl-dnstimer> process.

=item B<-help>

Prints a help message.

=item B<-verbose>

Prints verbose output.

=item B<-Version>

Prints B<owl-dnstimer>'s version and exit.

=back

=head1 SEE ALSO

B<owl-rrdata(1)>,
B<owl-rrsec(1)>,
B<owl-sensord(1)>

B<owl-config(5)>,
B<owl-data(5)>

=head1 COPYRIGHT

Copyright 2012-2014 SPARTA, Inc.  All rights reserved.

=head1 AUTHOR

Wayne Morrison, tewok@tislabs.com

=cut

