#!/usr/bin/perl
#
# Copyright 2006-2009 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.
#

use strict;

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

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: 0.9.1";
my $DTVERS = "DNSSEC-Tools Version: 1.5";

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

#
# Data required for command line options.
#
my %options = ();			# Filled option array.
my @opts =
(
	"halt",				# Shutdown rollerd.
	"display",			# Turn on rollerd's graphical display.
	"dspub=s",			# 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.
	"nodisplay",			# Turn off rollerd's graphical display.
	"rollallzsks",			# ZSK-roll all our zones.
	"rollksk=s",			# Roll the specified zone.
	"rollrec=s",			# Change the rollrec file.
	"rollzsk=s",			# ZSK roll the specified zone.
	"runqueue",			# Run the queue.
	"shutdown",			# Shutdown rollerd.
	"skipall",			# Stop all zones from rolling.
	"skipzone=s",			# Stop the named zone from rolling.
	"sleeptime=i",			# Set rollerd's sleep time.
	"status",			# Get rollerd's status.
	"zonelog=s",			# Set a zone's logging level.
	"zonestatus",			# Get status of zones.
	"zsargs",			# Set zonesigner args for some zones.

	"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 $dispflag;
my $dspubflag;
my $dspuballflag;
my $logfileflag;
my $loglevelflag;
my $nodispflag;
my $zrollallflag;
my $rollkskflag;
my $rollrecflag;
my $rollzskflag;
my $runqueueflag;
my $shutdownflag;
my $skipallflag;
my $skipzoneflag;
my $sleeptimeflag;
my $statusflag;
my $zonelogflag;
my $zonestatflag;
my $zsargsflag;

my $quiet;

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.

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

	#
	# 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(1);
	}

	#
	# Send commands for all the specified options.
	#
	if($dispflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_DISPLAY,1);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd display started\n";
		}
		else
		{
			print STDERR "rollerd display not started\n";
			$rcret++;
		}
	}
	elsif($dspubflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_DSPUB,$dspubflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd informed that parent has published DS record for zone $dspubflag\n";
		}
		else
		{
			print STDERR "rollerd not informed that parent has published DS record for zone $dspubflag\n";
			print STDERR "resp - <$dspubflag>\n";
			$rcret++;
		}
	}
	elsif($dspuballflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_DSPUBALL,$dspuballflag);

		($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 6\n";
		}
		else
		{
			print STDERR "rollerd not informed that parents have published DS record for all zones in KSK rollover phase 6\n";
			print STDERR "resp - <$dspubflag>\n";
			$rcret++;
		}
	}
	elsif($logfileflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_LOGFILE,$logfileflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd log file set to $logfileflag\n";
		}
		else
		{
			print STDERR "log-level set failed:  $resp\n";
			$rcret++;
		}
	}
	elsif($loglevelflag)
	{
		if(rolllog_validlevel($loglevelflag) == 0)
		{
			print STDERR "invalid rollerd log level: $loglevelflag\n";
			$rcret++;
		}
		else
		{
			rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_LOGLEVEL,$loglevelflag);

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "rollerd log level set to $loglevelflag\n";
			}
			else
			{
				print STDERR "log-level set failed:  $resp\n";
				$rcret++;
			}
		}
	}
	elsif($nodispflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_DISPLAY,0);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd display stopped\n";
		}
		else
		{
			print STDERR "rollerd display not stopped\n";
			$rcret++;
		}
	}
	elsif($zrollallflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_ROLLALL);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "all zones now in rollover:  $resp\n";
		}
		else
		{
			print STDERR "$resp";
			$rcret++;
		}
	}
	elsif($rollrecflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_ROLLREC,$rollrecflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd now using rollrec file $rollrecflag\n";
		}
		else
		{
			print STDERR "couldn't set rollrec file:  $resp\n";
			$rcret++;
		}
	}
	elsif($rollkskflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_ROLLKSK,$rollkskflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp\n";
		}
		else
		{
			print STDERR "unable to force KSK rollover process for $rollkskflag:  $resp\n";
			$rcret++;
		}
	}
	elsif($rollzskflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_ROLLZSK,$rollzskflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp\n";
		}
		else
		{
			print STDERR "unable to force ZSK rollover process for $rollzskflag:  $resp\n";
			$rcret++;
		}
	}
	elsif($runqueueflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_RUNQUEUE);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd checking rollrec queue\n";
		}
		else
		{
			#
			# Shouldn't ever get here...
			#
			print STDERR "couldn't force the rollrec queue:  $resp\n";
			$rcret++;
		}
	}
	elsif($skipallflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_SKIPALL,$skipzoneflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollover stopped for all zones:  $resp\n";
		}
		else
		{
			print STDERR "$resp";
			$rcret++;
		}
	}
	elsif($skipzoneflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_SKIPZONE,$skipzoneflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollover stopped for zone $skipzoneflag\n";
		}
		else
		{
			print STDERR "unable to stop rollover for zone $skipzoneflag:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($sleeptimeflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_SLEEPTIME,$sleeptimeflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd sleep time set to $sleeptimeflag\n";
		}
		else
		{
			print STDERR "sleep-time set failed:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($statusflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_STATUS);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp";
		}
		else
		{
			print STDERR "status failed:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($shutdownflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_SHUTDOWN);
		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "$resp\n";
		}
		else
		{
			print STDERR "shutdown failed:  \"$resp\"\n";
			$rcret++;
		}
	}
	elsif($zonelogflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_ZONELOG,$zonelogflag);

		($ret, $resp) = rollmgr_getresp();
		if($ret == ROLLCMD_RC_OKAY)
		{
			print "rollerd logging changed for $zonelogflag\n";
		}
		else
		{
			print STDERR "zonelog failed:  $resp\n";
			$rcret++;
		}
	}
	elsif($zonestatflag)
	{
		rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_ZONESTATUS);

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

			$zsargs = join ',', @ARGV;

			rollmgr_sendcmd(CHANNEL_WAIT,ROLLCMD_ZSARGS,$zsargs);

			($ret, $resp) = rollmgr_getresp();
			if($ret == ROLLCMD_RC_OKAY)
			{
				print "$resp";
			}
			else
			{
				print STDERR "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) || usage();

	#
	# Set our option variables based on the parsed options.
	#
	$dispflag	= $options{'display'}	  || 0;
	$nodispflag	= $options{'nodisplay'}	  || 0;
	$dspubflag	= $options{'dspub'}	  || 0;
	$dspuballflag	= $options{'dspuball'}	  || 0;
	$quiet		= $options{'quiet'}	  || 0;
	$logfileflag	= $options{'logfile'}	  || 0;
	$loglevelflag	= $options{'loglevel'}	  || 0;
	$zrollallflag	= $options{'rollallzsks'} || 0;
	$rollkskflag	= $options{'rollksk'}	  || 0;
	$rollrecflag	= $options{'rollrec'}	  || 0;
	$rollzskflag	= $options{'rollzsk'}	  || 0;
	$runqueueflag	= $options{'runqueue'}	  || 0;
	$shutdownflag	= ($options{'shutdown'}   || $options{'halt'}) || 0;
	$skipallflag	= $options{'skipall'}	  || 0;
	$skipzoneflag	= $options{'skipzone'}	  || 0;
	$sleeptimeflag	= $options{'sleeptime'}	  || 0;
	$statusflag	= $options{'status'}	  || 0;
	$zonelogflag	= $options{'zonelog'}	  || 0;
	$zonestatflag	= $options{'zonestatus'}  || 0;
	$zsargsflag	= $options{'zsargs'}	  || 0;
	$version        = $options{'Version'}     || 0;

	#
	# Ensure that only one command argument was given.
	# We'll get rid of the non-command options before checking.
	#
	delete($options{'quiet'});
	if(keys(%options) > 1)
	{
		print STDERR "only one argument may be specified per execution\n";
		exit(1);
	}

	#
	# 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();
	}

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

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

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

	exit(1);
}

#----------------------------------------------------------------------
# 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(1);
}

#-----------------------------------------------------------------------------
# Routine:	usage()
#
sub usage
{
	print STDERR "usage:  rollctl [options] \n";
	print STDERR "\t-halt			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-logfile <logfile>	set log file\n";
	print STDERR "\t-loglevel <loglevel>	set logging level\n";
	print STDERR "\t-nodisplay		stop graphical display\n";
	print STDERR "\t-rollallzsks		roll all zones\n";
	print STDERR "\t-rollksk <zone>		roll specified zone's KSK\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		shutdown rollerd\n";
	print STDERR "\t-skipall <zone>		skip named zone\n";
	print STDERR "\t-skipzone <zone>	skip named zone\n";
	print STDERR "\t-sleeptime <seconds>	set sleep time (in seconds)\n";
	print STDERR "\t-status			get rollerd's status\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			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.

=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>.

=item B<-dspuball>

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

=item B<-halt>

Cleanly halts B<rollerd> execution.

=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<-nodisplay>

Stops the rollover status GUI.

=item B<-rollallzsks>

Initiates ZSK rollover for all the zones defined in the current I<rollrec> file.

=item B<-rollksk zone>

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

=item B<-rollrec rollrec_file>

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

=item B<-rollzsk zone>

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

=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<-skipall>

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

=item B<-skipzone zone>

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

=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<-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<-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.

=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.

=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=1024 example.com
    # rollctl -zsargs =ksklen=1024 =zsklen=1024 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 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-2009 SPARTA, Inc.  All rights reserved.
See the COPYING file included with the DNSSEC-Tools package for details.

=head1 AUTHOR

Wayne Morrison, tewok@users.sourceforge.net

=head1 SEE ALSO

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

B<rollerd(8)>

=cut
