#!/usr/bin/perl
#
# Copyright 2006-2014 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
#
# rollset
#
#	This script sets some values in a rollrec file.
#

use strict;

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

use Net::DNS::SEC::Tools::rollrec;
use Net::DNS::SEC::Tools::rollmgr;
use Net::DNS::SEC::Tools::tooloptions;

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

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

#
# Data required for command line options.
#
my $verbose = 0;			# Verbose flag.
my $check   = 0;			# Check-rollrec flag.
my $doall   = 0;			# Edit all rollrec entries flag.

#
# Flag variables for options.
#
my $name	= '';
my $newname	= '';
my $zonename	= '';
my $zonefile	= '';
my $zonegroup	= '';
my $keyrec	= '';
my $zsargs	= '';
my $roll	= 0;
my $skip	= 0;
my $display	= 0;
my $nodisplay	= 0;
my $admin	= 0;
my $directory	= 0;
my $loglvl	= -1;
my $deladm	= 0;
my $deldir	= 0;
my $dellog	= 0;
my $delzsa	= 0;
my $delzg	= 0;
my $rstzn	= 0;

my %options = ();			# Filled option array.
my @opts =
(
	'name=s',			# Rollrec to edit.
	'roll',				# Make record a roll record.
	'skip',				# Make record a skip record.
	'newname=s',			# New rollrec name.
	'zonename=s',			# New zonename value.
	'zg=s',				# New zonegroup value.
	'file=s',			# New zonefile value.
	'keyrec=s',			# New keyrec value.
	'admin=s',			# New administrator email address.
	'directory=s',			# New directory for zone's files.
	'loglevel=s',			# New logging level value.
	'display',			# Turn on display flag.
	'nodisplay',			# Turn off display flag.
	'zsargs=s',			# New zonesigner arguments.
	'del-admin',			# Delete the administrator line.
	'del-directory',		# Delete the directory line.
	'del-loglevel',			# Delete the logging level line.
	'del-zg',			# Delete the zonegroup line.
	'del-zsargs',			# Delete the zonesigner arguments.
	'reset-zonename',		# Reset the zonename fields.

	'check',			# Run rollchk after edits.
	'verbose',			# Give lotsa output.
	'Version',			# Display the version number.
	'help',				# Give a usage message and exit.
);


my $rrf;				# Rollrec we're editing.

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

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

#-----------------------------------------------------------------------------
# Routine:	main()
#
# Purpose:	This routine controls everything.
#
sub main()
{
	#
	# Check our options.
	#
	doopts();

	#
	# Edit the rollrec file.
	#
	editrrf();

	#
	# Maybe check the rollrec file for validity.
	#
	system("rollchk $rrf") 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 $argc = @ARGV;		# Number of command line arguments.

	usage()   if($argc == 0);

	GetOptions(\%options,@opts) || usage();

	#
	# Set some flags based on the command line.
	#
	$name	   = $options{'name'};
	$newname   = $options{'newname'};
	$zonename  = $options{'zonename'};
	$zonefile  = $options{'file'};
	$zonegroup = $options{'zg'};
	$keyrec	   = $options{'keyrec'};
	$admin	   = $options{'admin'};
	$directory = $options{'directory'};
	$roll	   = $options{'roll'};
	$skip	   = $options{'skip'};
	$display   = $options{'display'};
	$nodisplay = $options{'nodisplay'};
	$loglvl	   = $options{'loglevel'}   if(defined($options{'loglevel'}));
	$zsargs	   = $options{'zsargs'};
	$deladm	   = $options{'del-admin'};
	$deldir	   = $options{'del-directory'};
	$dellog	   = $options{'del-loglevel'};
	$delzg	   = $options{'del-zg'};
	$delzsa	   = $options{'del-zsargs'};
	$rstzn	   = $options{'reset-zonename'};

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

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

	#
	# Ensure we're not being asked to do the impossible.
	# (The stupid, actually.)
	#
	if($roll && $skip)
	{
		print STDERR "-roll and -skip are mutually exclusive\n";
		exit(1);
	}
	if($display && $nodisplay)
	{
		print STDERR "-display and -nodisplay are mutually exclusive\n";
		exit(1);
	}
	if(defined($zsargs) && $delzsa)
	{
		print STDERR "-zsargs and -del-zsargs are mutually exclusive\n";
		exit(1);
	}
	if(defined($loglvl) && $dellog)
	{
		print STDERR "-loglevel and -del-loglevel are mutually exclusive\n";
		exit(1);
	}
	if(defined($zonegroup) && $delzg)
	{
		print STDERR "-zg and -del-zg are mutually exclusive\n";
		exit(1);
	}
	if(defined($zonename) && $rstzn)
	{
		print STDERR "-zonename and -reset-zonename are mutually exclusive\n";
		exit(1);
	}

	#
	# Ensure the logging level is valid.
	#
	if($loglvl > -1)
	{
		my $logtmp;				# Temporary log level.

		$logtmp = rolllog_num($loglvl);
		if($logtmp == -1)
		{
			print STDERR "loglevel $loglvl invalid\n";
			exit(3);
		}
		$loglvl = rolllog_str($logtmp);
	}

	#
	# Convert the zonesigner arguments into their real form.
	#
	if($zsargs ne '')
	{
		my @zsargs = split / /, $zsargs;

		for(my $ind=0; $ind < @zsargs; $ind++)
		{
			$zsargs[$ind] =~ s/^=/-/;
			$zsargs[$ind] =~ s/=/ /;
		}
		$zsargs = join ' ', @zsargs;
	}


	#
	# If a rollrec name wasn't specified, we'll apply the fix to every
	# rollrec in the file.
	#
	$doall = 1 if(! $name);

	#
	# Ensure that -rename will be applied to a specific zone.
	#
	if($newname && $doall)
	{
		print STDERR "-rename may only be used with a single zone\n";
		exit(5);
	}

	#
	# Remove leading and trailing whitespace, then ensure the name is
	# still there.
	#
	if(defined($zonegroup))
	{
		$zonegroup =~ s/^\s*//;
		$zonegroup =~ s/\s*$//;
		if(length($zonegroup) == 0)
		{
			print STDERR "you must specify a zonegroup that isn't all blanks\n";
			exit(6);
		}
	}

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

	#
	# Ensure we were given a rollrec file to check.
	#
	usage() if($argc == 0);

	#
	# Save the name of the rollrec file.
	#
	$rrf = $ARGV[0];
}

#-----------------------------------------------------------------------------
# Routine:	chkphase()
#
# Purpose:	This routine verifies a rollover phase number.  The phase
#		must be numeric and in the given range.
#
# Return Values:
#		1 is returned on valid phase number.
#		0 is returned on invalid phase number.
#
sub chkphase
{
	my $phase = shift;			# Phase to check.
	my $max   = shift;			# Phase's upper bound.

	my $ind;

	#
	# Check if the phase falls into the 0-to-$max range, returning
	# success if it does.
	#
	for(my $ind = 0; $ind <= $max; $ind++)
	{
		return(1) if($phase == $ind);
	}

	#
	# Return failure.
	#
	return(0);
}

#-----------------------------------------------------------------------------
# Routine:	editrrf()
#
# Purpose:	This routine reads a rollrec file and performs the requested
#		record updates.
#
sub editrrf
{
	#
	# Lock and load the rollrec file.
	#
	rollrec_lock();
	if(rollrec_read($rrf) < 0)
	{
		rollrec_unlock();
		print STDERR "unable to read rollrec file \"$rrf\"\n";
		exit(-1);
	}

	#
	# Ensure the specified rollrec name exists.
	#
	if(!$doall && (rollrec_exists($name) == 0))
	{
		print STDERR "invalid rollrec name \"$name\"\n";
		exit(7);
	}

	#
	# Change the rollrec name, if so requested.
	#
	if($newname)
	{
		my $ret;				# Rename return code.

		$ret = rollrec_rename($name,$newname);
		if($ret == 0)
		{
			if($verbose)
			{
				print "name:  rollrec name changed to \"$newname\"\n";
			}
		}
		elsif($ret == -3)
		{
			print STDERR "unable to rename \"$name\"; $name is not a valid rollrec name\n";
		}
		elsif($ret == -4)
		{
			print STDERR "unable to rename \"$name\"; $newname is already a rollrec name\n";
		}
		elsif($ret == -5)
		{
			print STDERR "unable to rename \"$name\"\n";
		}
	}

	#
	# Go through the rollrecs and apply the needed changes.
	#
	foreach my $rname (rollrec_names())
	{
		#
		# Go to the next record if we aren't doing everything or if
		# this isn't the specified record.
		#
		next if(!$doall && ($rname ne $name));

		#
		# Set the rollrec record's type as requested by the user.
		#
		typer($roll,$rname,'roll');
		typer($skip,$rname,'skip');

		#
		# Set the rollrec fields as requested by the user.
		#
		changer($zonename,$rname,'zonename',$zonename);
		changer($zonefile,$rname,'zonefile',$zonefile);
		changer($zonegroup,$rname,'zonegroup',$zonegroup);
		changer($keyrec,$rname,'keyrec',$keyrec);
		changer($admin,$rname,'administrator',$admin);
		changer($directory,$rname,'directory',$directory);
		changer($zsargs,$rname,'zsargs',$zsargs);
		changer($display,$rname,'display',1);
		changer($nodisplay,$rname,'display',0);
		changer($loglvl,$rname,'loglevel',$loglvl)    if($loglvl > -1);

		#
		# Delete some fields.
		#
		deleter($deladm,$rname,'administrator');
		deleter($deldir,$rname,'directory');
		deleter($dellog,$rname,'loglevel');
		deleter($delzg,$rname,'zonegroup');
		deleter($delzsa,$rname,'zsargs');

		#
		# Reset some fields.
		#
		changer($rstzn,$rname,'zonename',$rname);

		#
		# If we aren't messing with all the rollrecs then we're
		# done because we've found the requested record.
		#
		last if(!$doall);
	}

	#
	# Close, write, and unlock the rollrec file.
	#
	rollrec_close();
	rollrec_unlock();

}

#----------------------------------------------------------------------
# Routine:	typer()
#
# Purpose:	Change a rollrec record's type to roll or skip.
#
sub typer
{
	my $flag    = shift;				# Flag value.
	my $rname   = shift;				# Rollrec name.
	my $rectype = shift;				# New record type.

	#
	# Do nothing if this record type shouldn't be changed.
	#
	return if(!$flag);

	#
	# If the verbose flag was given, we'll give a message.
	#
	print "$rname:  changing record type to \"$rectype\"\n" if($verbose);

	#
	# Change the rollrec record's type.
	#
	rollrec_rectype($rname,$rectype);
}

#----------------------------------------------------------------------
# Routine:	changer()
#
# Purpose:	Change a rollrec field value and maybe give verbose messages.
#
sub changer
{
	my $flag  = shift;				# Flag value.
	my $rname = shift;				# Rollrec name.
	my $field = shift;				# Field to change.
	my $val	  = shift;				# Field's new value.

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

	#
	# If the verbose flag was given, we'll show the old value and
	# the new value.
	#
	if($verbose)
	{
		my $oldval;				# Old value.

		$oldval = rollrec_recval($rname,$field);
		print "$rname:  changing $field \"$oldval\" to \"$val\"\n";
	}

	#
	# Change the rollrec field's value.
	#
	rollrec_setval($rname,$field,$val);

}

#----------------------------------------------------------------------
# Routine:	deleter()
#
# Purpose:	Delete a rollrec field and maybe give verbose messages.
#
sub deleter
{
	my $flag  = shift;				# Flag value.
	my $rname = shift;				# Rollrec name.
	my $field = shift;				# Field to delete.

	my $val;					# Value of field.

	#
	# Do nothing if this field shouldn't be deleted.
	#
	return if(!$flag);

	#
	# Ensure the field exists for the rollrec.
	#
	$val = rollrec_recval($rname,$field);
	if(!defined($val))
	{
		print "$rname does not have a $field field\n" if(!$doall);
		return;
	}

	#
	# If the verbose flag was given, we'll show the current value.
	#
	print "$rname:  deleting $field \"$val\"\n" if($verbose);

	#
	# Delete the rollrec field.
	#
	rollrec_delfield($rname,$field);
}

#----------------------------------------------------------------------
# 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:  rollset [options] <rollrec-file>\n";
	print STDERR "	options:\n";

	print STDERR "		-name rollrec-name\n";
	print STDERR "		-roll\n";
	print STDERR "		-skip\n";
	print STDERR "		-newname new-zone-name\n";
	print STDERR "		-zonename zone-name\n";
	print STDERR "		-zg zonegroup-name\n";
	print STDERR "		-file zone-file\n";
	print STDERR "		-keyrec keyrec-file\n";
	print STDERR "		-admin admin-email\n";
	print STDERR "		-directory zone-directory\n";
	print STDERR "		-loglevel logging-level\n";
	print STDERR "		-zsargs zonesigner-arguments\n";
	print STDERR "		-display\n";
	print STDERR "		-nodisplay\n";
	print STDERR "		-del-admin\n";
	print STDERR "		-del-directory\n";
	print STDERR "		-del-loglevel\n";
	print STDERR "		-del-zg\n";
	print STDERR "		-del-zsargs\n";
	print STDERR "		-reset-zonename\n";

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

	exit(0);
}

1;

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

=pod

=head1 NAME

rollset - Modifies entries in a DNSSEC-Tools I<rollrec> file

=head1 SYNOPSIS

  rollset [options] rollrec-file

=head1 DESCRIPTION

B<rollset> modifies fields in the I<rollrec> file specified by
I<rollrec-file>.  Multiple options may be combined in a single B<rollset>
execution.  B<rollset> operates quietly unless it is given the I<-verbose>
option.

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

=head1 OPTIONS

=over 4

=item B<-admin addr>

The zone administrator's email address is set to I<addr>.

=item B<-check>

If this option is given, the B<rollchk> command will be run on the modified
I<rollrec> file.

=item B<-del-admin>

The I<administrator> line is deleted.
The B<-admin> and B<-del-admin> options are mutually exclusive.

=item B<-del-directory>

The I<directory> line is deleted.
The B<-directory> and B<-del-directory> options are mutually exclusive.

=item B<-del-loglevel>

The I<loglevel> line is deleted.
The B<-loglevel> and B<-del-loglevel> options are mutually exclusive.

=item B<-del-zg>

The I<zonegroup> line is deleted.
The B<-zg> and B<-del-zg> options are mutually exclusive.

=item B<-del-zsargs>

The I<zsargs> line is deleted.
The B<-zsargs> and B<-delzsargs> options are mutually exclusive.

=item B<-directory dir>

The directory to hold the zone's files is set to I<dir>.

=item B<-display>

Turn on the GUI display of the zones in the selected I<rollrec>s.
This option is mutually exclusive of the I<-nodisplay> option.

=item B<-file zone-file>

The zone file in the selected I<rollrec> records is modified to be
I<zone-file>.

=item B<-keyrec keyrec-file>

The I<keyrec> file in the selected I<rollrec> records is modified to be
I<keyrec-file>.

=item B<-loglevel logging-level>

The logging level of the selected I<rollrec> records is set to
I<logging-level>.  The valid logging levels are defined in I<rollmgr.pm(3)>.

=item B<-name rollrec-name>

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

=item B<-nodisplay>

Turn off the GUI display of the zones in the selected I<rollrec>s.
This option is mutually exclusive of the B<-display> option.

=item B<-rename>

The I<rollrec>'s name is changed to match the specified name.
The new name cannot be the name of an existing I<rollrec> in the file.
This option must be used in conjunction with the B<-name> option.

=item B<-reset-zonename>

The I<zonename> field is set to match the name of the I<rollrec> record.
This option is mutually exclusive of the B<-zonename> option.

=item B<-roll>

Convert the selected I<rollrec>s to be active ("roll") records.
This option is mutually exclusive of the B<-skip> option.

=item B<-skip>

Convert the selected I<rollrec>s to be inactive ("skip") records.
This option is mutually exclusive of the B<-roll> option.

=item B<-zg zonegroup-name>

The zonegroup in the selected I<rollrec> records is modified to be
I<zonegroup-name>.  This field may contain a single zonegroup name or a
comma-separated list of names.  If a list is given, then there may be
whitespace before or after each name, but it is not recognized as being part
of the zonegroup name itself.  Whitespace I<within> the name is considered
part of the name.  Zonegroup names are considered case insensitive.  The
names may be given with whatever case the user desires, but names whose only
differences are the case of the letters are considered to be the same name.

=item B<-zonename zone-name>

The zone name in the selected I<rollrec> records is modified to be
I<zone-name>.

=item B<-zsargs arglist>

Provides additional B<zonesigner> arguments.  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>.  If more than one argument is given,
the set of arguments should be enclosed in quotes.

Given the B<rollset> 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<rollset> translates these arguments to the appropriate format for
B<zonesigner>.  These examples should clarify the argument modifications:

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

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

    # rollset -zsargs =ksklength=2048 example.rollrec
    # rollset -zsargs "=ksklen=2048 =zsklen=2048" example.rollrec

The B<-zsargs> and B<-delzsargs> options are mutually exclusive.

=item B<-verbose>

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

=item B<-Version>

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

=item B<-help>

Display a usage message.

=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<lsroll(8)>,
B<rollchk(8)>,
B<rollerd(8)>,
B<rollinit(8)>

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

B<file-rollrec(5)>

=cut
