#!/usr/bin/perl
#
# Copyright 2012-2014 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
#
# keymod
#
#	This script modifies key parameters in a keyrec file.
#	The new key parameters will be used by zonesigner in future executions.
#

use strict;

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

use Net::DNS::SEC::Tools::keyrec;
use Net::DNS::SEC::Tools::tooloptions;

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

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

my %options = ();			# Filled option array.
my @opts =
(
	'zone=s',				# Zone to edit.
	'kskcount=i',				# New kskcount.
	'ksklength=i',				# New ksklength.
	'ksklife=i',				# New ksklife.
	'random=s',				# New random.
	'revperiod=i',				# New revperiod.
	'zskcount=i',				# New zskcount.
	'zsklength=i',				# New zsklength.
	'zsklife=i',				# New zsklife.

	'nocheck',				# Don't run krfcheck after edit.
	'verbose',				# Give lotsa output.
	'Version',				# Display the version number.
	'help',					# Give a usage message and exit.
);

#
# Flag variables for options.
#
my $zone;
my $random;
my $revperiod;

my $kskcnt;
my $ksklen;
my $zsklen;
my $zskcnt;
my $ksklife;
my $zsklife;

#
# Data required for command line options.
#
my $verbose	= 0;					# Verbose flag.
my $check	= 1;					# No-check flag.

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

my $zonemods;					# Count of mods made to a zone.
my $ret;					# Return code from main().

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

#-----------------------------------------------------------------------------
# Routine:	main()
#
# Purpose:	This routine controls everything.
#
sub main()
{
	my $krf;					# Keyrec we're editing.

	#
	# Check our options.
	#
	doopts();

	#
	# Modify the keyrec file and validate the changes.
	#
	foreach $krf (@ARGV)
	{
		editkrf($krf);

		#
		# Maybe check the keyrec file for validity.
		#
		system("krfcheck $krf") if($check);
	}

	return(0);
}

#-----------------------------------------------------------------------------
# Routine:	doopts()
#
# Purpose:	This routine gets the options from the command line and does
#		a bit of validity checking.
#
sub doopts
{
	my $errs = 0;						# Error count.

	#
	# Ensure we were given a keyrec file to check.
	#
	usage()   if(@ARGV == 0);

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

	#
	# Show the version number or help info if requested.
	#
	version() if(defined($options{'Version'}));
	usage()   if(defined($options{'help'}));

	#
	# Set some flags based on the command line.
	#
	$zone	   = $options{'zone'};

	$kskcnt    = $options{'kskcount'};
	$ksklen    = $options{'ksklength'};
	$ksklife   = $options{'ksklife'};
	$random	   = $options{'random'};
	$revperiod = $options{'revperiod'};
	$zskcnt    = $options{'zskcount'};
	$zsklen    = $options{'zsklength'};
	$zsklife   = $options{'zsklife'};

	$check	   = $options{'check'};
	$verbose   = $options{'verbose'};

	#
	# Ensure we're not being asked to do the impossible.
	# (The stupid, actually.)
	#
	if(defined($kskcnt) && ($kskcnt < 0))
	{
		print STDERR "KSK length must not be negative\n";
		$errs++;
	}
	if(defined($ksklen) && ($ksklen < 0))
	{
		print STDERR "KSK length must not be negative\n";
		$errs++;
	}
	if(defined($ksklife) && ($ksklife < 0))
	{
		print STDERR "KSK life must not be negative\n";
		$errs++;
	}
	if(defined($revperiod) && ($revperiod < 0))
	{
		print STDERR "revocation period must not be negative\n";
		$errs++;
	}
	if(defined($zskcnt) && ($zskcnt < 0))
	{
		print STDERR "ZSK length must not be negative\n";
		$errs++;
	}
	if(defined($zsklen) && ($zsklen < 0))
	{
		print STDERR "ZSK length must not be negative\n";
		$errs++;
	}
	if(defined($zsklife) && ($zsklife < 0))
	{
		print STDERR "ZSK life must not be negative\n";
		$errs++;
	}

	#
	# Exit if we hit any errors.
	#
	exit(1) if($errs);

	#
	# Delete the non-command options and ensure that we were given
	# something to do.
	#
	delete $options{'zone'};
	delete $options{'nocheck'};
	delete $options{'verbose'};
	if(keys(%options) == 0)
	{
		print STDERR "you must specify something to be changed\n";
		exit(2);
	}

	if(@ARGV == 0)
	{
		print STDERR "no keyrec file specified\n";
		exit(3);
	}

}

#-----------------------------------------------------------------------------
# Routine:	editkrf()
#
# Purpose:	This routine reads a keyrec file and copies the keyrec
#		records into either the roll hash or the skip hash,
#		depending on each record's type.  Any unrecognized keyrec
#		entries are reported. 
#
sub editkrf
{
	my $krf = shift;				# Keyrec file to modify.

	#
	# Load the keyrec file.
	#
	if(keyrec_read($krf) < 0)
	{
		print STDERR "unable to read keyrec file \"$krf\"\n";
		exit(4);
	}

	#
	# Go through the keyrecs and apply the needed changes.
	#
	foreach my $zname (keyrec_names())
	{
		my $kt;						# Keyrec's type.

		#
		# Go to the next record if:
		#	- this isn't a zone record
		#	- we aren't doing everything and
		#	  this isn't the specified record
		#
		$kt = keyrec_recval($zname,'keyrec_type');
		next if($kt ne 'zone');
		next if(($zone ne '') && ($zname ne $zone));

		#
		# Set the keyrec fields as requested by the user.
		#
		$zonemods = 0;
		setter($zname,'new_kskcount',$kskcnt);
		setter($zname,'new_ksklength',$ksklen);
		setter($zname,'new_ksklife',$ksklife);
		setter($zname,'new_random',$random);
		setter($zname,'new_revperiod',$revperiod);
		setter($zname,'new_zskcount',$zskcnt);
		setter($zname,'new_zsklength',$zsklen);
		setter($zname,'new_zsklife',$zsklife);

		print "zone $zname updated\n" if($zonemods);
	}

	#
	# Close and write the keyrec file.
	#
	keyrec_close();
}

#----------------------------------------------------------------------
# Routine:	setter()
#
# Purpose:	Set a new value for a key parameter.
#		If the new value is zero (or null), then the field will
#		be deleted from the keyrec..
#
sub setter
{
	my $zname = shift;				# Keyrec name.
	my $field = shift;				# Field to change.
	my $val	  = shift;				# Field's new value.
	my $oldval;					# Old value.

	#
	# Do nothing if this field shouldn't be changed.
	#
	return if(! defined($val));

	#
	# If the verbose flag was given, we'll show the old value and
	# the new value.
	#

	#
	# Change the keyrec field's value or delete the keyrec field.
	#
	if($val)
	{
		if($verbose)
		{
			$oldval = keyrec_recval($zname,$field);
			print "$zname:  changing $field \"$oldval\" to \"$val\"\n";
		}

		keyrec_setval('zone',$zname,$field,$val);
	}
	else
	{
		if($verbose)
		{
			$oldval = keyrec_recval($zname,$field);
			print "$zname:  deleting $field \"$oldval\"\n";
		}

		keyrec_delval($zname,$field);
	}
	$zonemods++;
}

#----------------------------------------------------------------------
# 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:  keymod [options] <keyrec files>\n";
	print STDERR "	options:\n";

	print STDERR "		-zone zonename\n";
	print STDERR "		-kskcount kskcount\n";
	print STDERR "		-ksklength ksklength\n";
	print STDERR "		-ksklife ksklife\n";
	print STDERR "		-random random\n";
	print STDERR "		-revperiod revperiod\n";
	print STDERR "		-zskcount zskcount\n";
	print STDERR "		-zsklength zsklength\n";
	print STDERR "		-zsklife zsklife\n";

	print STDERR "		-nocheck\n";
	print STDERR "		-verbose\n";
	print STDERR "		-Version\n";
	print STDERR "		-help\n";

	exit(0);
}

1;

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

=pod

=head1 NAME

keymod - Modifies key parameters in a DNSSEC-Tools I<keyrec> file

=head1 SYNOPSIS

  keymod [options] keyrec1 ... keyrecN

=head1 DESCRIPTION

B<keymod> modifies the key parameters in a keyrec file that are used to
generate cryptographics keys used to sign zones.  The new parameters
will be used by B<zonesigner> when generating I<new> keys.  It has no
effect on existing keys.

B<zonesigner> will use the new parameter for a zone the next time it
generates a key that requires that parameter.  This means that, for example,
a new ZSK length will not be used during the I<next> invocation of
B<zonesigner> if that invocation will be performing KSK-rollover actions.

The following fields may be modified:

    kskcount - count of KSK keys
    ksklength - length of KSK keys
    ksklife - lifetime of KSK keys
    random - random number generator device file
    revperiod - revocation period for KSK keys
    zskcount - count of ZSK keys
    zsklength - length of ZSK keys
    zsklife - lifetime of ZSK keys

New key/value fields will be added to a zone I<keyrec> file to inform
B<zonesigner> that new values should be used.  The key portion of the added
fields will begin with "new_".  For example, a new KSK length of 2048 will
be written to the I<keyrec> file as:

    new_ksklength        2048

All zone records in the specified I<keyrec> file will be modified, unless the
B<-zone> option is given.  In that case, only the named zone will be modified.

If a zone I<keyrec> already contains a new key/value field, then the value
will be modified on subsequent runs of B<keymod>.

=head1 OPTIONS

B<keymod> recognizes the following options.  Multiple options may be combined
in a single B<keymod> execution.

All numeric values must be positive or zero.

If a new key/value field should be deleted from a zone I<keyrec>, then a
zero or empty string value should be specified for the appropriate option.

=over 4

=item B<-zone zonename>

The zone I<keyrec> whose name matches I<zonename> is selected as the only
I<keyrec> that will be modified.  If this name is not given, then all zone
I<keyrec> records will be modified.

=item B<-ksklength ksklength>

The I<ksklength> field will be modified in the selected I<keyrec> records
to the given value.  This is a numeric field whose values depend on the
cryptographic algorithm to be used to generate keys for the zone.

=item B<-kskcount kskcount>

The I<kskcount> field will be modified in the selected I<keyrec> records to the
given value.  This is a numeric field.

=item B<-ksklife ksklife>

The I<ksklife> field will be modified in the selected I<keyrec> records to the
given value.  This is a numeric field.

=item B<-random random>

The I<random> field will be modified in the selected I<keyrec> records to the
given value.  This is a text field that will be passed to the key generator.

=item B<-revperiod revperiod>

The I<revperiod> field will be modified in the selected I<keyrec> records to
the given value.  This is a numeric field.

=item B<-zskcount zskcount>

The I<zskcount> field will be modified in the selected I<keyrec> records to the
given value.  This is a numeric field.

=item B<-zsklength zsklength>

The I<zsklength> field will be modified in the selected I<keyrec> records
to the given value.  This is a numeric field whose values depend on the
cryptographic algorithm to be used to generate keys for the zone.

=item B<-zsklife zsklife>

The I<zsklife> field will be modified in the selected I<keyrec> records to the
given value.  This is a numeric field.

=item B<-nocheck>

If this option is given, the B<krfcheck> command will B<not> be run on
the modified I<keyrec> file.

=item B<-verbose>

Display information about every modification made to the I<keyrec> file.

=item B<-Version>

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

=item B<-help>

Display a usage message.

=back

=head1 COPYRIGHT

Copyright 2012-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<zonesigner(8)>,
B<krfcheck(8)>

B<Net::DNS::SEC::Tools::keyrec.pm(3)>

B<file-keyrec(5)>

=cut

