#!/usr/pkg/bin/perl
#
# Copyright 2006-2014 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
#
# rollctl
#
#	This script controls the rollover daemon.
#	See the pod for more details.
#

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


use strict;

use Getopt::Long qw(:config no_ignore_case_always);
# use Getopt::Long qw(:config no_ignore_case_always debug);
# $Getopt::Long::debug = 1;

use Net::DNS::SEC::Tools::conf;
use Net::DNS::SEC::Tools::rollmgr;
use Net::DNS::SEC::Tools::rolllog;
use Net::DNS::SEC::Tools::tooloptions;

#
# Version information.
#
my $NAME   = "rollctl";
my $VERS   = "$NAME version: 2.1.0";
my $DTVERS = "DNSSEC-Tools Version: 2.2.3";

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

#
# Data required for command line options.
#
my %options = ();			# Filled option array.
my @opts =
(
	"halt:s",			# Shutdown rollerd.
	"display",			# Turn on rollerd's graphical display.
	"dspub",			# Parent has published a DS record.
	"dspuball",			# Parents have published DS records.
	"logfile=s",			# Set rollerd's log file.
	"loglevel:s",			# Set rollerd's logging level.
	"logtz=s",			# Set rollerd's logging timezone.
	"mergerrfs",			# Merge a set of rollrec files.
	"phasemsg=s",			# Set rollerd's phase-message length.
	"pidfile=s",			# pid storage file.
	"nodisplay",			# Turn off rollerd's graphical display.
	"rollall",			# Resume all suspended zones.
	"rollallksks",			# KSK-roll all our zones.
	"rollallzsks",			# ZSK-roll all our zones.
	"rollksk",			# KSK roll the specified zone(s).
	"rollrec=s",			# Change the rollrec file.
	"rollzone",			# Restart the suspended, named zone(s).
	"rollzsk",			# ZSK roll the specified zone(s).
	"runqueue",			# Run the queue.
	"queuelist",			# Get list of zones in the soon queue.
	"queuestatus",			# Status of queue-soon event handler.
	"shutdown:s",			# Shutdown rollerd.
	"skipall",			# Stop all zones from rolling.
	"skipzone",			# Stop the named zone(s) from rolling.
	"signzone",			# Sign only the named zone(s).
	"signzones",			# Sign all the zones.
	"sleeptime=i",			# Set rollerd's sleep time.
	"splitrrf",			# Split a rollrec file in two.
	"status",			# Get rollerd's status.
	"zonegroup:s",			# Get a list of current zone groups.
	"zonelog",			# Set a zone's/zones' logging level.
	"zonestatus",			# Get status of zones.
	"zsargs",			# Set zonesigner args for some zones.

	"group",			# Apply command to zone group.

	"Version",			# Display the version number.
	"quiet",			# Don't print anything.
	"help",				# Give a usage message and exit.
);

#
# Flags for the options.  Variable/option mapping should obvious.
#
my $commandcount    = 0;

my $dispflag	    = 0;
my $dspubflag	    = 0;
my $dspuballflag    = 0;
my $groupflag	    = 0;
my $krollallflag    = 0;
my $logfileflag	    = 0;
my $loglevelflag    = 0;
my $logtzflag	    = 0;
my $logphasemsg	    = '';
my $mergerrfsflag   = 0;
my $nodispflag	    = 0;
my $rollallflag	    = 0;
my $rollkskflag	    = 0;
my $rollrecflag	    = 0;
my $rollzoneflag    = 0;
my $rollzskflag	    = 0;
my $runqueueflag    = 0;
my $queuelistflag   = 0;
my $queuestatusflag = 0;
my $shutdownflag    = 0;
my $signzoneflag    = 0;
my $signzonesflag   = 0;
my $skipallflag	    = 0;
my $skipzoneflag    = 0;
my $sleeptimeflag   = 0;
my $splitrrfflag    = 0;
my $statusflag	    = 0;
my $zonegroupflag   = undef;
my $zonelogflag	    = 0;
my $zonestatflag    = 0;
my $zrollallflag    = 0;
my $zsargsflag	    = 0;
my $pidfile	    = '';

my $quiet	    = 0;
my $version	    = 0;		# Display the version number.

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


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

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

#-----------------------------------------------------------------------------
# Routine:	main()
#
# Purpose:	Yeah, yeah, a main() isn't necessary.  However, it offends my
#		sense of aesthetics to have great gobs of code on the same
#		level as a pile of globals.
#
#		But what about all those globals, you ask...
#
sub main()
{
	my $argc = @ARGV;		# Number of command line arguments.

	my $rcret = 0;			# Return code for rollctl.
	my $ret;			# Return code from rollerd.
	my $resp;			# Response message from rollerd.
	my $olderr;			# Saved error action.

	#
	# Use the packed config file if we're running as part of a packed
	# configuration.
	#
	if(runpacked())
	{
		setconffile("$ENV{'PAR_TEMP'}/inc/dnssec-tools.conf");
	}

	#
	# Check our options.  All the commands are alphabetized, except
	# for shutdown.  We'll save that for last.
	#
	doopts($argc);

	#
	# Run our configuration checks and make sure any error messages
	# will be printed.
	#
	$olderr = erraction(ERR_MSG);
	if(dt_confcheck(0) > 0)
	{
		print STDERR "rollctl:  configuration checks failed, can't send commands\n";
		exit(201);
	}
	erraction($olderr);

	#
	# If rollerd isn't running, we'll give an error message and exit.
	# Some rollmgr_running() implementations may not be fool-proof.
	#
	if(rollmgr_running() != 1)
	{
		print STDERR "rollerd is not running\n";
		exit(200);
	}

	#
	# Send commands for all the specified options.
	#
	if($dispflag)
	{
		if(! sendcmd(ROLLCMD_DISPLAY,1))
		{
			print STDERR "rollctl:  error sending command DISPLAY\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if(($ret == ROLLCMD_RC_OKAY) && ($ret ne ""))
		{
			print "rollerd display started\n";
		}
		else
		{
			print "rollerd display not started\n";
			$rcret++;
		}
	}
	elsif($dspubflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "rollctl: -dspub missing zone argument\n";
			exit(1);
		}

		foreach my $zone (@ARGV)
		{
			if(! sendcmd(ROLLCMD_DSPUB,$zone))
			{
				print STDERR "rollctl:  error sending command DSPUB($zone)\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "rollerd informed that parent has published DS record for zone $zone\n";
			}
			else
			{
				print "$resp\n";
				$rcret++;
			}
		}

	}
	elsif($dspuballflag)
	{
		if(! sendcmd(ROLLCMD_DSPUBALL,$dspuballflag))
		{
			print STDERR "rollctl:  error sending command DSPUBALL\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd informed that parents have published DS record for all zones in KSK rollover phase 5\n";
		}
		else
		{
			print "$resp\n";
			$rcret++;
		}
	}
	elsif($logfileflag)
	{
		if(! sendcmd(ROLLCMD_LOGFILE,$logfileflag))
		{
			print STDERR "rollctl:  error sending command LOGFILE\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd log file set to $logfileflag\n";
		}
		else
		{
			print "log-level set failed:  $resp\n";
			$rcret++;
		}
	}
	elsif($loglevelflag)
	{
		if(rolllog_validlevel($loglevelflag) == 0)
		{
			print STDERR "invalid rollerd log level: $loglevelflag\n";
			$rcret++;
		}
		else
		{
			if(! sendcmd(ROLLCMD_LOGLEVEL,$loglevelflag))
			{
				print STDERR "rollctl:  error sending command LOGLEVEL\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "rollerd log level set to $loglevelflag\n";
			}
			else
			{
				print "log-level set failed:  $resp\n";
				$rcret++;
			}
		}
	}
	elsif($logtzflag)
	{
		if(! sendcmd(ROLLCMD_LOGTZ,$logtzflag))
		{
			print STDERR "rollctl:  error sending command LOGTZ\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd log timezone set to $logtzflag\n";
		}
		else
		{
			print "log-tz set failed:  $resp\n";
			$rcret++;
		}
	}
	elsif($logphasemsg)
	{
		if(($logphasemsg !~ /long/i) && ($logphasemsg !~ /short/i))
		{
			print STDERR "invalid rollerd phase-message length: $logphasemsg\n";
			$rcret++;
		}
		else
		{
			if(! sendcmd(ROLLCMD_PHASEMSG,$logphasemsg))
			{
				print STDERR "rollctl:  error sending command PHASEMSG\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "rollerd phasemsg to $logphasemsg\n";
			}
			else
			{
				print "phasemsg set failed:  $resp\n";
				$rcret++;
			}
		}
	}
	elsif($mergerrfsflag)
	{
		my $rrfs = join(':', @ARGV);

		if(! sendcmd(ROLLCMD_MERGERRFS,$rrfs))
		{
			print STDERR "rollctl:  error sending command MERGERRFS\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd merged the rollrec files\n";
		}
		else
		{
			print "rollerd did not merge the rollrec files\n";
			$rcret++;
		}
	}
	elsif($nodispflag)
	{
		if(! sendcmd(ROLLCMD_DISPLAY,0))
		{
			print STDERR "rollctl:  error sending command DISPLAY\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd display stopped\n";
		}
		else
		{
			print "rollerd display not stopped\n";
			$rcret++;
		}
	}
	elsif($rollallflag)
	{
		if(! sendcmd(ROLLCMD_ROLLALL))
		{
			print STDERR "rollctl:  error sending command ROLLALL\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "all suspended zones now resumed:  $resp\n";
		}
		else
		{
			print "$resp";
			$rcret++;
		}
	}
	elsif($krollallflag)
	{
		if(! sendcmd(ROLLCMD_ROLLALLKSKS))
		{
			print STDERR "rollctl:  error sending command ROLLALLKSKS\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "all zones now in KSK rollover:  $resp\n";
		}
		else
		{
			print "$resp";
			$rcret++;
		}
	}
	elsif($zrollallflag)
	{
		if(! sendcmd(ROLLCMD_ROLLALLZSKS))
		{
			print STDERR "rollctl:  error sending command ROLLALLZSKS\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "all zones now in ZSK rollover:  $resp\n";
		}
		else
		{
			print "$resp";
			$rcret++;
		}
	}
	elsif($rollrecflag)
	{
		if(! sendcmd(ROLLCMD_ROLLREC,$rollrecflag))
		{
			print STDERR "rollctl:  error sending command ROLLREC\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd now using rollrec file $rollrecflag\n";
		}
		else
		{
			print "couldn't set rollrec file:  $resp\n";
			$rcret++;
		}
	}
	elsif($rollkskflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "rollctl: -rollksk missing zone argument\n";
			exit(2);
		}

		foreach my $zone (@ARGV)
		{
			if(! sendcmd(ROLLCMD_ROLLKSK,$zone))
			{
				print STDERR "rollctl:  error sending command ROLLKSK($zone)\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "$resp\n";
			}
			else
			{
				print "unable to force KSK rollover process for $zone:  $resp\n";
				$rcret++;
			}
		}
	}
	elsif($rollzoneflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "rollctl: -rollzone missing zone argument\n";
			exit(2);
		}

		foreach my $zone (@ARGV)
		{
			if(! sendcmd(ROLLCMD_ROLLZONE,$zone))
			{
				print STDERR "rollctl:  error sending command ROLLZONE($zone)\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "rollover restarted for zone $zone\n";
			}
			else
			{
				print "unable to restart rollover for zone $zone:  \"$resp\"\n";
				$rcret++;
			}
		}
	}
	elsif($rollzskflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "rollctl: -rollzsk missing zone argument\n";
			exit(2);
		}

		foreach my $zone (@ARGV)
		{
			if(! sendcmd(ROLLCMD_ROLLZSK,$zone))
			{
				print STDERR "rollctl:  error sending command ROLLZSK($zone)\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "$resp\n";
			}
			else
			{
				print "unable to force ZSK rollover process for $zone:  $resp\n";
				$rcret++;
			}
		}
	}
	elsif($runqueueflag)
	{
		if(! sendcmd(ROLLCMD_RUNQUEUE))
		{
			print STDERR "rollctl:  error sending command RUNQUEUE\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd checking rollrec queue\n";
		}
		else
		{
			#
			# Shouldn't ever get here...
			#
			print "couldn't force the rollrec queue:  $resp\n";
			$rcret++;
		}
	}
	elsif($queuelistflag)
	{
		if(! sendcmd(ROLLCMD_QUEUELIST))
		{
			print STDERR "rollctl:  error sending command QUEUELIST\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp";
		}
		else
		{
			print "rollerd error response:  <$resp>\n";
			$rcret++;
		}
	}
	elsif($queuestatusflag)
	{
		if(! sendcmd(ROLLCMD_QUEUESTATUS))
		{
			print STDERR "rollctl:  error sending command QUEUESTATUS\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp";
		}
		else
		{
			print "rollerd error response:  <$resp>\n";
			$rcret++;
		}
	}
	elsif($signzoneflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "rollctl: -signzone missing zone argument\n";
			exit(2);
		}

		foreach my $zone (@ARGV)
		{
			if(! sendcmd(ROLLCMD_SIGNZONE,$zone))
			{
				print STDERR "rollctl:  error sending command SIGNZONE($zone)\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "zone $zone signed\n";
			}
			else
			{
				print "unable to sign zone $zone:  \"$resp\"\n";
				$rcret++;
			}
		}
	}
	elsif($signzonesflag)
	{
		my $flag = shift @ARGV;

		if(($flag ne 'all') && ($flag ne 'active'))
		{
			print STDERR "rollctl:  -signzones must be given the \"all\" or \"active\" argument\n";
			exit(1);
		}

		if(! sendcmd(ROLLCMD_SIGNZONES,$flag))
		{
			print STDERR "rollctl:  error sending command SIGNZONES\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "all zones signed\n";
		}
		else
		{
			print "unable to sign all zones:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($skipallflag)
	{
		if(! sendcmd(ROLLCMD_SKIPALL,$skipzoneflag))
		{
			print STDERR "rollctl:  error sending command SKIPALL\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollover stopped for all zones:  $resp\n";
		}
		else
		{
			print "$resp";
			$rcret++;
		}
	}
	elsif($skipzoneflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "rollctl: -skipzone missing zone argument\n";
			exit(2);
		}

		foreach my $zone (@ARGV)
		{
			if(! sendcmd(ROLLCMD_SKIPZONE,$zone))
			{
				print STDERR "rollctl:  error sending command SKIPZONE($zone)\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "rollover stopped for zone $zone\n";
			}
			else
			{
				print "unable to stop rollover for zone $zone:  \"$resp\"\n";
				$rcret++;
			}
		}
	}
	elsif($sleeptimeflag)
	{
		if(! sendcmd(ROLLCMD_SLEEPTIME,$sleeptimeflag))
		{
			print STDERR "rollctl:  error sending command SLEEPTIME\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd sleep time set to $sleeptimeflag\n";
		}
		else
		{
			print "sleep-time set failed:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($splitrrfflag)
	{
		my $rrfs = join(':', @ARGV);

		if(! sendcmd(ROLLCMD_SPLITRRF,$rrfs))
		{
			print STDERR "rollctl:  error sending command SPLITRRF\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd split the rollrec file\n";
		}
		else
		{
			print "rollerd did not split the rollrec files\n";
			$rcret++;
		}
	}
	elsif($statusflag)
	{
		if(! sendcmd(ROLLCMD_STATUS))
		{
			print STDERR "rollctl:  error sending command ZONELOG\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp";
		}
		else
		{
			print "status failed:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($shutdownflag)
	{
		if($shutdownflag == 2)
		{
			my $pid = rollmgr_getid();
			if(kill("INT", $pid) < 1)
			{
				print STDERR "rollctl:  unable to send immediate SHUTDOWN\n";
				exit(1);
			}

			print "rollctl:  immediate SHUTDOWN notice sent to rollerd\n";
			exit(0);
		}

		if(! sendcmd(ROLLCMD_SHUTDOWN))
		{
			print STDERR "rollctl:  error sending command SHUTDOWN\n";
			exit(1);
		}
		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp\n";
		}
		else
		{
			print "shutdown failed:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif(defined($zonegroupflag))
	{
		if(! sendcmd(ROLLCMD_ZONEGROUP,$zonegroupflag))
		{
			print STDERR "rollctl:  error sending command ZONEGROUP\n";
			exit(1);
		}
		($ret, $resp) = rollmgr_getresp($zonegroupflag);
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp\n";
		}
		else
		{
			print "$resp\n";
			$rcret++;
		}
	}
	elsif($zonelogflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "rollctl: -zonelog missing zone:loglevel argument\n";
			exit(2);
		}

		foreach my $zone (@ARGV)
		{
			if($zone !~ /.+\:.+/)
			{
				print STDERR "rollctl:  improperly formed zone:loglevel pair\n";
				next;
			}

			if(! sendcmd(ROLLCMD_ZONELOG,$zone))
			{
				print STDERR "rollctl:  error sending command ZONELOG($zone)\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "rollerd logging changed for $zone\n";
			}
			else
			{
				print "zonelog failed:  $resp\n";
				$rcret++;
			}
		}
	}
	elsif($zonestatflag)
	{
		if(! sendcmd(ROLLCMD_ZONESTATUS))
		{
			print STDERR "rollctl:  error sending command ZONESTATUS\n";
			exit(1);
		}

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			zonestatus("$resp");
		}
		else
		{
			print "zonestatus failed:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($zsargsflag)
	{
		if(@ARGV == 0)
		{
			print STDERR "zoneargs failed:  arguments are required\n";
			exit(2);
		}
		else
		{
			my $zsargs;			# Zonesigner arguments.

			$zsargs = join ',', @ARGV;

			if(! sendcmd(ROLLCMD_ZSARGS,$zsargs))
			{
				print STDERR "rollctl:  error sending command ZSARGS\n";
				exit(1);
			}

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "$resp";
			}
			else
			{
				print "zsarg failed:  \"$resp\"\n";
				$rcret++;
			}
		}
	}

	return($rcret);
}

#-----------------------------------------------------------------------------
# Routine:	doopts()
#
# Purpose:	This routine shakes and bakes our command line options.
#		A bunch of option variables are set according to the specified
#		options.  Then a little massaging is done to make sure that
#		the proper actions are taken.  A few options imply others, so
#		the implied options are set if the implying options are given.
#
sub doopts
{
	my $argc = shift;			# Command line argument count.
	my $argcnt;				# Specified-argument count.

	#
	# Give a usage flag if there aren't any options.
	#
	usage() if($argc == 0);

	#
	# Parse the options.
	#
	GetOptions(\%options,@opts, verbose => 1) || usage();

	#
	# Give a usage flag if asked.
	#
	usage() if(defined($options{'help'}));

	#
	# Set our option variables based on the parsed options.
	#
	$quiet = $options{'quiet'} || 0;

	#
	# Command Options
	#

	if(exists($options{'display'}))
	{
		$dispflag = $options{'display'};
		$commandcount++;
	}
	if(exists($options{'dspub'}))
	{
		$dspubflag = $options{'dspub'};
		$commandcount++;
	}
	if(exists($options{'dspuball'}))
	{
		$dspuballflag = $options{'dspuball'};
		$commandcount++;
	}
	if(exists($options{'group'}))
	{
		$groupflag = $options{'group'};
	}
	if(exists($options{'logfile'}))
	{
		$logfileflag = $options{'logfile'};
		$commandcount++;
	}
	if(exists($options{'loglevel'}))
	{
		$loglevelflag = $options{'loglevel'};
		$commandcount++;
	}
	if(exists($options{'logtz'}))
	{
		$logtzflag = $options{'logtz'};
		$commandcount++;
	}
	if(exists($options{'mergerrfs'}))
	{
		$mergerrfsflag = 1;
		$commandcount++;
	}
	if(exists($options{'nodisplay'}))
	{
		$nodispflag = $options{'nodisplay'};
		$commandcount++;
	}
	if(exists($options{'phasemsg'}))
	{
		$logphasemsg = $options{'phasemsg'};
		$commandcount++;
	}
	if(exists($options{'pidfile'}))
	{
		$pidfile = $options{'pidfile'};
		rollmgr_set_idfile($pidfile) if($pidfile ne '');
	}
	if(exists($options{'queuelist'}))
	{
		$queuelistflag = $options{'queuelist'};
		$commandcount++;
	}
	if(exists($options{'queuestatus'}))
	{
		$queuestatusflag = $options{'queuestatus'};
		$commandcount++;
	}
	if(exists($options{'rollall'}))
	{
		$rollallflag = $options{'rollall'};
		$commandcount++;
	}
	if(exists($options{'rollallksks'}))
	{
		$krollallflag = $options{'rollallksks'};
		$commandcount++;
	}
	if(exists($options{'rollallzsks'}))
	{
		$zrollallflag = $options{'rollallzsks'};
		$commandcount++;
	}
	if(exists($options{'rollksk'}))
	{
		$rollkskflag = $options{'rollksk'};
		$commandcount++;
	}
	if(exists($options{'rollrec'}))
	{
		$rollrecflag = $options{'rollrec'};
		$commandcount++;
	}
	if(exists($options{'rollzone'}))
	{
		$rollzoneflag = $options{'rollzone'};
		$commandcount++;
	}
	if(exists($options{'rollzsk'}))
	{
		$rollzskflag = $options{'rollzsk'};
		$commandcount++;
	}
	if(exists($options{'runqueue'}))
	{
		$runqueueflag = $options{'runqueue'};
		$commandcount++;
	}
	if(exists($options{'shutdown'}) || exists($options{'halt'}))
	{
		$shutdownflag =	1;

		if(($options{'shutdown'} eq 'now') || ($options{'halt'} eq 'now'))
		{
			$shutdownflag = 2;
		}

		$commandcount++;
	}
	if(exists($options{'signzone'}))
	{
		$signzoneflag = $options{'signzone'};
		$commandcount++;
	}
	if(exists($options{'signzones'}))
	{
		$signzonesflag = $options{'signzones'};
		$commandcount++;
	}
	if(exists($options{'skipall'}))
	{
		$skipallflag = $options{'skipall'};
		$commandcount++;
	}
	if(exists($options{'skipzone'}))
	{
		$skipzoneflag = $options{'skipzone'};
		$commandcount++;
	}
	if(exists($options{'sleeptime'}))
	{
		$sleeptimeflag = $options{'sleeptime'};
		$commandcount++;
	}
	if(exists($options{'splitrrf'}))
	{
		$splitrrfflag = 1;
		$commandcount++;
	}
	if(exists($options{'status'}))
	{
		$statusflag = $options{'status'};
		$commandcount++;
	}
	if(exists($options{'zonegroup'}))
	{
		$zonegroupflag = $options{'zonegroup'};
		$commandcount++;
	}
	if(exists($options{'zonelog'}))
	{
		$zonelogflag = $options{'zonelog'};
		$commandcount++;
	}
	if(exists($options{'zonestatus'}))
	{
		$zonestatflag = $options{'zonestatus'};
		$commandcount++;
	}
	if(exists($options{'zsargs'}))
	{
		$zsargsflag = $options{'zsargs'};
		$commandcount++;
	}
	if(exists($options{'Version'}))
	{
		$version = $options{'Version'};
		$commandcount++;
	}

	#
	# Ensure that only one command argument was given.
	# We'll get rid of the non-command options before checking.
	#

	if($commandcount > 1)
	{
		print STDERR "only one command argument may be specified per execution\n";
		exit(3);
	}
	elsif($commandcount < 1)
	{
		print STDERR "a command argument must be specified\n";
		exit(3);
	}

	#
	# Close our output descriptors if the -quiet option was given.
	#
	if($quiet)
	{
		close(STDOUT);
		close(STDERR);
	}

	#
	# Show the version number if requested.
	#
	version() if($version);

	#
	# Show the logging levels if one wasn't specified.
	#
	if(defined($options{'loglevel'}) && ($options{'loglevel'} eq ''))
	{
		showloglevels();
	}

	#
	# Ensure that conflicting options weren't given.
	#
	if($dispflag && $nodispflag)
	{
		print STDERR "-display and -nodisplay are mutually exclusive\n";
		exit(1);
	}
}

#----------------------------------------------------------------------
# Routine:	zonestatus()
#
# Purpose:	Print the version number(s) and exit.
#
sub zonestatus
{
	my $resp = shift;			# Full response from rollerd.

	my @lines;				# Line-split responses.
	my @rnames = ();			# Rollrec names.
	my @znames = ();			# Zone names.
	my @states = ();			# Roll states.
	my @phases = ();			# Roll phases.

	my $rml = -1;				# Max. length of rollrec names.
	my $zml = -1;				# Max. length of zone names.

	#
	# Divide the pieces of the each response line into the data arrays.
	#
	@lines = split /\n/, $resp;
	foreach my $line (@lines)
	{
		my $len;
		my @slices = split /;/, $line;

		#
		# Save the atoms.
		#
		push @rnames, $slices[0];
		push @znames, $slices[1];
		push @states, $slices[2];
		push @phases, $slices[3];

		#
		# Save the length of the longest rollrec name.
		#
		$len = length($slices[0]);
		$rml = $len if($len > $rml);

		#
		# Save the length of the longest zone name.
		#
		$len = length($slices[1]);
		$zml = $len if($len > $zml);
	}

	#
	# Pretty-print the zone-status data from rollerd.
	#
	for(my $ind=0; $ind < @rnames; $ind++)
	{
		printf "%-$rml.*s    %-$zml.*s    $states[$ind]  $phases[$ind]\n",
			$rml, $rnames[$ind],
			$zml, $znames[$ind],
	}
}

#----------------------------------------------------------------------
# Routine:	sendcmd()
#
# Purpose:	Send the command to rollerd.  We'll also prepend the
#		group-command indicator if -group was given.
#
sub sendcmd
{
	my $cmd = shift;			# Command to send rollerd.
	my $arg = shift;			# Command's optional argument.
	my $ret;				# Return code.

	$cmd = ROLLMGR_GROUP . $cmd if($groupflag);

	$ret = rollmgr_sendcmd(CHANNEL_WAIT,$cmd,$arg);
	return($ret);
}


#----------------------------------------------------------------------
# Routine:	showloglevels()
#
# Purpose:	Print the logging levels and exit.
#
sub showloglevels
{
	my @levels = rolllog_levels();			# Valid logging levels.

	print "valid rollerd logging levels:\n";

	foreach my $level (@levels)
	{
		my $lnum;				# Numeric logging level.

		$lnum = rolllog_num($level);
		print "\t$level\t\t($lnum)\n";
	}

	exit(0);
}

#----------------------------------------------------------------------
# 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:  rollctl [options] \n";
	print STDERR "\t-halt [now]		  shutdown rollerd\n";
	print STDERR "\t-display		  start graphical display\n";
	print STDERR "\t-dspub <zone>		  parent has published DS record for zone\n";
	print STDERR "\t-dspuball		  parents have published DS records for zones\n";
	print STDERR "\t-group			  apply command to zone group\n";
	print STDERR "\t-logfile <logfile>	  set log file\n";
	print STDERR "\t-loglevel <loglevel>	  set logging level\n";
	print STDERR "\t-logtz <log-timezone>	  set logging timezone\n";
	print STDERR "\t-phasemsg <length>	  set phase-message length\n";
	print STDERR "\t-pidfile <pidfile>	  set rollerd's process-id file\n";
	print STDERR "\t-nodisplay		  stop graphical display\n";
	print STDERR "\t-rollall		  restart all suspended zones\n";
	print STDERR "\t-rollallzsks		  roll all zones\n";
	print STDERR "\t-rollksk <zone>		  roll specified zone's KSK\n";
	print STDERR "\t-rollzone <zone>	  restart named suspended zone\n";
	print STDERR "\t-rollzsk <zone>		  roll named zone\n";
	print STDERR "\t-rollrec <rollrec>	  set rollrec file\n";
	print STDERR "\t-runqueue		  run queue\n";
	print STDERR "\t-shutdown [now]		  shutdown rollerd\n";
	print STDERR "\t-signzone <zone>	  sign named zone (no key rollover)\n";
	print STDERR "\t-signzones [all | active] sign zones (no key rollover)\n";
	print STDERR "\t-skipall		  skip all zones\n";
	print STDERR "\t-skipzone <zone>	  skip named zone\n";
	print STDERR "\t-splitrrf <rrf entries>	  split the current rollrec file\n";
	print STDERR "\t-sleeptime <seconds>	  set sleep time (in seconds)\n";
	print STDERR "\t-status			  get rollerd's status\n";
	print STDERR "\t-zonegroup [zonegroup]	  show zone groups\n";
	print STDERR "\t-zonelog		  set a zone's log level\n";
	print STDERR "\t-zonestatus		  get status of zones\n";
	print STDERR "\t-zsargs	<args> <zone>	  set zonesigner arguments for zones\n";
	print STDERR "\t-Version		  display version number\n";
	print STDERR "\t-quiet			  don't give any output\n";
	print STDERR "\t-help			  help message \n";
	exit(0);
}

1;

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

=pod

=head1 NAME

rollctl - Send commands to the DNSSEC-Tools rollover daemon

=head1 SYNOPSIS

  rollctl [options]

=head1 DESCRIPTION

The B<rollctl> command sends commands to the DNSSEC-Tools rollover daemon,
B<rollerd>.  Only one option may be specified on a command line.

In most cases, B<rollerd> will send a response to B<rollctl>.  B<rollctl> will
print a success or failure message, as appropriate.

If B<rollctl> is run as a PAR-packed command, it will use its own local copy
of the B<dnssec-tools.conf> file.  This file will be found in the package
directory.

=head1 OPTIONS

The following options are handled by B<rollctl>.

=over 4

=item B<-display>

Starts the rollover status GUI.

=item B<-dspub zone>

Indicates that I<zone>'s parent has published a new DS record for I<zone>.

Multiple zones can be specified on the command line.
For instance, this command will send the I<dspub> command to B<rollerd>
for three zones.

    $ rollctl -dspub example1.com example2.com example3.com

=item B<-dspuball>

Indicates that DS records have been published for all zones in phase 5 of
KSK rollover.

=item B<-group>

Indicates that the specified command should apply to a zone group instead of
a zone.  Consequently, the specified zone must actually be a zone group.
This option must be used in conjunction with another command.

This option only applies to the following commands: B<-dspub>, B<-rollksk>, 
B<-rollzone>, B<-rollzsk>, and B<-skipzone>.  This command will have no
effect if it is given to other other commands.

=item B<-halt> [now]

Cleanly halts B<rollerd> execution.  If the optional I<now> parameter is
given, then B<rollerd> will be halted immediately, rather than allowing it to
complete its currently queued operations.

=item B<-logfile logfile>

Sets the B<rollerd> log file to I<logfile>.
This must be a valid logging file, meaning that if I<logfile> already
exists, it must be a regular file.  The only exceptions to this are if
I<logfile> is B</dev/stdout> or B</dev/tty>.

=item B<-loglevel loglevel>

Sets the B<rollerd> logging level to I<loglevel>.
This must be one of the valid logging levels defined in B<rollmgr.pm(3)>.

If a logging level is not specified, then the list of valid levels will be
printed and B<rollctl> will exit.  The list is given in both text and numeric
forms.

=item B<-logtz logtz>

Sets the B<rollerd> logging timezone to I<loglevel>.  This must be either
I<gmt> (for Greenwich Mean Time or I<local> (for the host's local time.)

=item B<-mergerrfs rollrec0 ... rollrecN>

Tells B<rollerd> to merge the specified I<rollrec> files with its active
I<rollrec> file.  The names of the I<rollrec> files must not contain colons.

=item B<-nodisplay>

Stops the rollover status GUI.

=item B<-phasemsg length>

B<length> is the default length of phase-related log messages used by
B<rollerd>.  The valid levels are "long" and "short", with "long" being
the default value.

The long message length means that a phase description will be included with
some log messages.  For example, the long form of a message about ZSK
rollover
phase 3 will look like this:  "ZSK phase 3 (Waiting for old zone data to
expire from caches)".

The short message length means that a phase description will not be included
with some log messages.  For example, the short form of a message about ZSK
rollover phase 3 will look like this:  "ZSK phase 3".

=item B<-rollall>

Resumes rollover for all zones in the current I<rollrec> file that have been
suspended.  ("skip" zones are suspended.)

=item B<-rollallksks>

Initiates KSK rollover for all the zones defined in the current I<rollrec>
file that aren't currently in rollover.

=item B<-rollallzsks>

Initiates ZSK rollover for all the zones defined in the current I<rollrec>
file that aren't currently in rollover.

=item B<-rollksk zone>

Initiates KSK rollover for the zone named by I<zone>.

Multiple zones can be specified on the command line.
For instance, this command will send the I<rollksk> command to B<rollerd>
for three zones.

    $ rollctl -rollksk example1.com example2.com example3.com

=item B<-rollrec rollrec_file>

Sets the I<rollrec> file to be processed by B<rollerd> to I<rollrec_file>.

=item B<-rollzone zone>

Resumes rollover for the suspended zone named by I<zone>.

Multiple zones can be specified on the command line.
For instance, this command will send the I<rollzone> command to B<rollerd>
for three zones.

    $ rollctl -rollzone example1.com example2.com example3.com

=item B<-rollzsk zone>

Initiates rollover for the zone named by I<zone>.

Multiple zones can be specified on the command line.
For instance, this command will send the I<rollzsk> command to B<rollerd>
for three zones.

    $ rollctl -rollzsk example1.com example2.com example3.com

=item B<-runqueue>

Wakes up B<rollerd> and has it run its queue of I<rollrec> entries.

=item B<-shutdown>

Synonym for B<-halt>.

=item B<-signzone zone>

Signs I<zone>'s zonefile without performing any rollover actions.  The zone
is signed with the keys most recently used to sign the zone.  No new keys
will be generated.

=item B<-signzones [all | active]>

Signs the zonefiles of zones managed by B<rollerd>, without performing any
rollover actions.  If the B<all> option is given, then all of B<rollerd>'s
zones will be signed.  If the B<active> option is given, then only those zones
which aren't in the I<skip> stage will be signed.  The zones are signed with
the keys most recently used to sign each zone.  No new keys will be generated.

=item B<-skipall>

Suspends rollover for all zones in the current I<rollrec> file.

=item B<-skipzone zone>

Suspends rollover for the zone named by I<zone>.

Multiple zones can be specified on the command line.
For instance, this command will send the I<skipzone> command to B<rollerd>
for three zones.

    $ rollctl -skipzone example1.com example2.com example3.com

=item B<-sleeptime seconds>

Sets B<rollerd>'s sleep time to I<seconds> seconds.  I<sleeptime> must be an
integer at least as large as the B<$MIN_SLEEP> value in B<rollerd>.

=item B<-splitrrf new-rrf zone0 ... zoneN>

Tells B<rollerd> to move a set of I<rollrec> entries from the current
I<rollrec> file into a new file.  The new file is named in the I<new-rrf>
parameter.  The I<rollrec> entries whose names correspond to the I<zone0> to
I<zoneN> list are moved to the new file.  The name of the new I<rollrec> file
and the zone names must not contain colons.

=item B<-status>

Has B<rollerd> write several of its operational parameters to its log file.
The parameters are also reported to B<rollctl>, which prints them to the
screen.

=item B<-zonegroup> I<[zone-group]>

Requests information about zone groups from B<rollerd>.  If the optional
I<zone-group> argument is not given, then B<rollerd> will return a list of
the defined zone groups and the number of zones in each.  If a I<zone-group>
is specified, then B<rollerd> will return a list of the zones in that group.

(While this is using the term "zone", it is actually referring to the name
of the I<rollrec> entries.  For a particular I<rollrec> entry, the I<rollrec>
name is usually the same as the zone name, but this is not a requirement.)

=item B<-zonelog>

Set the logging level for the specified zone.  The new logging level is only
for the current execution of B<rollerd> and is not saved to the active
I<rollrec> file.

The arguments for this command must be in the format "zone:loglevel".
For example, this command will send the I<zonelog> command to B<rollerd>
for three zones.

    $ rollctl -zonelog example1.com:info example2.com:6 example3.com:err

=item B<-zonestatus>

Has B<rollerd> write the status of zones in the current I<rollrec> file to the
B<rollerd> log file.  The status is also reported to B<rollctl>, which prints
it to the screen.  B<rollctl> prints it in columnar fashion to enhance
readability.  The columns, in order, are:  rollrec name, zone name, roll/skip
state, and rollover phase.

Example:
    anothersub                      anothersub.example.com  skip  KSK 1
    example.com                     example.com             roll  KSK 1
    site1.in.subzone.example.com    subzone.example.com     roll  KSK 3
    site1.subzone.example.com       subzone.example.com     roll  KSK 3

=item B<-zsargs arglist zones>

Provides additional B<zonesigner> arguments for a given set of zones.  These
arguments will override the arguments in the DNSSEC-Tools defaults file, the
DNSSEC-Tools configuration file, and the zones' I<keyrec> files.

The B<zonesigner> argument list is given in I<arglist>.  Given the B<rollctl>
argument processing, the new arguments for B<zonesigner> cannot be specified
as expected.  Instead, the arguments should be given in the following manner.
The leading dash should be replaced with an equals sign.  If the option takes
an argument, the space that would separate the option from the option's
argument should also be replaced by an equals sign.  B<rollerd> translates
these arguments to the appropriate format for B<zonesigner>.  These examples
should clarify the modifications:

    normal zonesigner option		-zsargs options
    ------------------------		---------------
	-nokrfile			   =nokrfile
	-zskcount 5			   =zskcount=5

The I<zones> list is a space-separated list of zones.  B<All> the new
B<zonesigner> arguments will be applied to B<all> the listed zones.

The "=clear" argument is special.  B<rollerd> translates it to "-clear",
which is not a normal B<zonesigner> option.  Instead, B<rollerd> recognizes
"-clear" as an indicator that it should remove the I<zsargs> field from the
I<rollrec> records for the specified zones.

The following are valid uses of B<-zsargs>:

    # rollctl -zsargs =ksklength=2048 example.com
    # rollctl -zsargs =ksklen=2048 =zsklen=2048 example.com test.com

=item B<-Version>

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

=item B<-quiet>

Prevents output from being given.  Both error and non-error output is
stopped.

=item B<-help>

Displays a usage message.

=back

=head1 EXIT CODES

B<rollctl> may give the following exit codes:

=over 4

=item 0 - Successful execution

=item 1 - Error sending the command to B<rollerd>.

=item 2 - Missing argument.

=item 3 - Too many commands specified.

=item 200 - Rollerd is not running.

=item 201 - Configuration file checks failed.

=back

=head1 FUTURE

The following modifications may be made in the future:

=over 4

=item command execution order

The commands will be executed in the order given on the command line rather
than in alphabetical order.

=back

=head1 COPYRIGHT

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

=head1 AUTHOR

Wayne Morrison, tewok@tislabs.com

=head1 SEE ALSO

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

B<rollerd(8)>

=cut
