#!/usr/bin/perl
#
# Copyright 2006-2008 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";
my $DTVERS = "DNSSEC-Tools Version: 1.4.1";

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

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

	"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 $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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
			}
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}
	if($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++;
		}
	}

	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.

	#
	# 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;
	$version        = $options{'Version'}     || 0;

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

	#
	# Show the version number if requested.
	#
	version() if(defined($options{'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:	version()
#
# 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-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>.  Multiple options may be specified on a single command line and
they will be executed in I<alphabetical> order.  The exception to this
ordering is that the I<-shutdown> command will always be executed last.

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<-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-2008 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
