#!/usr/bin/perl
#
# Copyright 2006-2011 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
# DNSSEC Tools
#
#	Rollrec file routines.
#
#	The routines in this module manipulate a rollrec file for the DNSSEC-
#	Tools.  The rollrec file contains information about key rollover
#	status.
#
#	Entries in the configuration file are of the "key value" format, with
#	the value enclosed in quotes.  Comments may be included by prefacing
#	them with the '#' or ';' comment characters.
#
#	These entries are grouped by the zone whose key(s) are being rolled
#	over.
#
#	An example rollrec file follows:
#
#	    roll "example.com"
#		zonename	"example.com"
#		zonefile	"/usr/etc/dnssec-tools/zones/db.example.com"
#		keyrec		"/usr/etc/dnssec-tools/keyrec/example.keyrec"
#		zskphase	"2"
#		kskphase	"0"
#		maxttl		"86400"
#		curerrors	"0"
#		display		"0"
#		phasestart	"Wed Mar 09 21:49:22 2005"
#
#	    roll "example2.com"
#		zonename	"example2.com"
#		zonefile	"/usr/etc/dnssec-tools/zone/db.example2.com"
#		keyrec		"/usr/etc/dnssec-tools/keyrec/example2.keyrec"
#		kskphase	"1"
#		zskphase	"0"
#		maxttl		"100000"
#		display		"1"
#		loglevel	"info"
#		curerrors	"0"
#		maxerrors	"5"
#		phasestart	"Sun Jan 01 16:00:00 2005"
#
#
#
#	The current implementation assumes that only one rollrec file will
#	be open at a time.  If module use proves this to be a naive assumption
#	this module will have to be rewritten to account for it.
#
#	The directory rollrec field was an excellent suggestion made by
#	Otmar Lendl <ol@bofh.priv.at>.  Several changes here, in rollerd,
#	and in other scripts are a result of this suggestion.
#

package Net::DNS::SEC::Tools::rollrec;

require Exporter;
use strict;

use Fcntl qw(:DEFAULT :flock);

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

our $VERSION = "1.9";
our $MODULE_VERSION = "1.9.0";

our @ISA = qw(Exporter);

our @EXPORT = qw(
			rollrec_add
			rollrec_close
			rollrec_default
			rollrec_del
			rollrec_delfield
			rollrec_discard
			rollrec_dump_array
			rollrec_dump_hash
			rollrec_exists
			rollrec_fields
			rollrec_fullrec
			rollrec_init
			rollrec_lock
			rollrec_merge
			rollrec_names
			rollrec_newrec
			rollrec_read
			rollrec_readfiles
			rollrec_rectype
			rollrec_recval
			rollrec_rename
			rollrec_settime
			rollrec_setval
			rollrec_split
			rollrec_unlock
			rollrec_write
		);

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

#
# Default file names.
#
my $DEFAULT_DNSSECTOOLS_DIR = getconfdir();
my $DEFAULT_ROLLREC = "dnssec-tools.rollrec";
my $LOCKNAME = "rollrec.lock";

#
# Valid fields in a rollrec.
#
my @ROLLFIELDS = (
			'zonename',
			'zonefile',
			'keyrec',
			'maxttl',
			'administrator',
			'directory',
			'phasestart',
			'display',
			'loglevel',
			'kskphase',
			'zskphase',
			'zsargs',
			'curerrors',
			'maxerrors',
			'ksk_rollsecs',
			'ksk_rolldate',
			'zsk_rollsecs',
			'zsk_rolldate',
		  );

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

my @rollreclines;		# Rollrec lines.
my $rollreclen;			# Number of rollrec lines.

my %rollrecs;			# Rollrec hash table (keywords/values.)

my $modified;			# File-modified flag.

#--------------------------------------------------------------------------
# Routine:	rollrec_lock()
#
# Purpose:	Lock rollrec processing so that only one process reads a
#		rollrec file at a time.
#
#		The actual rollrec file is not locked; rather, a synch-
#		ronization file is locked.  We lock in this manner due to
#		the way the rollrec module's functionality is spread over
#		a set of routines.
#
sub rollrec_lock
{
	my $lockdir;			# Configuration file directory.
	my $lockfile;			# Name of the lock file.

# print "rollrec_lock:  down in\n";

	#
	# Get the DNSSEC-Tools config directory.
	#
	$lockdir = makelocalstatedir("run/dnssec-tools") || $DEFAULT_DNSSECTOOLS_DIR;

	#
	# Build our lock file.
	#
	$lockfile = "$lockdir/$LOCKNAME";

	#
	# Open (and create?) our lock file.
	#
	if(!sysopen(RRLOCK,$lockfile,O_RDONLY|O_CREAT))
	{
#		err("unable to open lock file \"$lockfile\"; not locking...\n",-1);
		return(0);
	}

	#
	# Lock the lock file.
	#
	return(flock(RRLOCK,LOCK_EX));
}

#--------------------------------------------------------------------------
# Routine:	rollrec_unlock()
#
# Purpose:	Unlock rollrec processing so that other processes may read
#		a rollrec file.
#
sub rollrec_unlock
{
# print "rollrec_unlock:  down in\n";

	#
	# Unlock the lock file.
	#
	return(flock(RRLOCK,LOCK_UN));
}

#--------------------------------------------------------------------------
# Routine:	rollrec_read()
#
# Purpose:	Read a DNSSEC-Tools rollrec file.  The contents are read into
#		the @rollreclines array and the rollrecs are broken out into
#		the %rollrecs hash table.
#
sub rollrec_read
{
	my $rrf = shift;		# Rollover record file.
	my $rrcnt;			# Number of rollrecs we found.
	my @sbuf;			# Buffer for stat().

# print "rollrec_read:  down in\n";

	#
	# Use the default rollrec file, unless the caller specified
	# a different file.
	#
	$rrf = rollrec_default() if($rrf eq "");

	#
	# Make sure the rollrec file exists.
	#
	if(! -e $rrf)
	{
		err("rollrec file $rrf does not exist\n",-1);
		return(-1);
	}

	#
	# If a rollrec file is already open, we'll flush our buffers and
	# save the file.
	#
	@sbuf = stat(ROLLREC);
	rollrec_close() if(@sbuf != 0);

	#
	# Open up the rollrec file.  If we can't open it for reading and
	# writing, we'll try to open it just for reading.
	#
	if(open(ROLLREC,"+< $rrf") == 0)
	{
		if(open(ROLLREC,"< $rrf") == 0)
		{
			err("unable to open $rrf\n",-1);
			return(-2);
		}
	}

	#
	# Initialize some data.
	#
	rollrec_init();

	#
	# Read the contents of the specified rollrec file.
	#
	rollrec_readfile(*ROLLREC);

	#
	# Return the number of rollrecs we found.
	#
	$rrcnt = keys(%rollrecs);
	return($rrcnt);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_merge()
#
# Purpose:	Merge a set of DNSSEC-Tools rollrec files.  The contents are
#		read into the @rollreclines array and the rollrecs are broken
#		out into the %rollrecs hash table.
#
sub rollrec_merge
{
	my $firstrrf = shift;		# First rollrec file in list.
	my @rrflist = @_;		# Set of rollrec files.

	my %allrrfs = ();		# All rollrec filenames to be merged.
	my $errs = 0;			# Count of non-fatal errors.
	my $rrcnt;			# Number of rollrecs we found.

# print "rollrec_merge:  down in\n";

	#
	# Make sure a set of rollrec files was specified.
	#
	if((! defined($firstrrf)) || (@rrflist == 0))
	{
		err("no rollrec files given to merge\n",1);
		return(-1);
	}

	#
	# Build a hash of the rollrec file names and the number of times
	# each is specified.
	#
	foreach my $fn ($firstrrf, @rrflist)
	{
		$allrrfs{$fn}++;
	}

	#
	# Bump the error count if a file was listed multiple times.
	#
	foreach my $fn (keys(%allrrfs))
	{
		$errs++ if($allrrfs{$fn} > 1);
	}

	#
	# If any file was listed multiple times, given an error and return.
	#
	if($errs)
	{
		err("unable to merge rollrec files since some rollrec files were given multiple times\n",1);
		return(-5);
	}

	#
	# Create a target file if the first rollrec file doesn't exist.
	#
	if(! -e $firstrrf)
	{
		if(open(TMP,"> $firstrrf") == 0)
		{
			err("rollrec_merge:  unable to create target rollrec file \"$firstrrf\"\n",1);
			return(-2);
		}
		close(TMP);
	}

	#
	# Read the first rollrec file.  This will also zap all our current
	# internal data.
	#
	$allrrfs{$firstrrf}++;
	if(rollrec_read($firstrrf) < 0)
	{
		return(-3);
	}

	#
	# Read each remaining rollrec file and add it to our internal
	# rollrec collection.
	#
	foreach my $rrf (@rrflist)
	{
		#
		# Make sure the rollrec file exists.
		#
		if(! -e $rrf)
		{
			err("rollrec file \"$rrf\" does not exist\n",1);
			$errs++;
			next;
		}

		#
		# Close the needed file handle.
		#
		close(ROLLREC_MERGE);

		#
		# Open up the rollrec file.
		#
		if(open(ROLLREC_MERGE,"< $rrf") == 0)
		{
			err("unable to open $rrf\n",1);
			$errs++;
			next;
		}

		#
		# Read the contents of the specified rollrec file.
		#
		if(rollrec_readfile(*ROLLREC_MERGE) < 0)
		{
			$errs++;
		}
	}

	#
	# Close the file handle.
	#
	close(ROLLREC_MERGE);

	#
	# If we encountered errors while merging the files, we'll give
	# an error and reset ourself.
	#
	if($errs)
	{
		err("unable to merge rollrec files due to errors\n",1);
		rollrec_init();
		return(-4);
	}

	#
	# Write the new rollrec file.
	#
	$modified = 1;
	rollrec_write();

	#
	# Return the number of rollrecs we found.
	#
	$rrcnt = keys(%rollrecs);
	return($rrcnt);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_split()
#
# Purpose:	Split a rollrec file in two.  A list of rollrec entries will
#		be removed from the current rollrec file and appended to
#		another file.  The rollrec entries will be removed from
#		@rollreclines and %rollrecs.
#
#		The rollrecs will be appended to the destination file.
#
sub rollrec_split
{
	my $newrrf = shift;		# New rollrec file.
	my @rrlist = @_;		# Set of rollrec names.

	my $valid = 0;			# Count of valid names.
	my @badnames = ();		# List of invalid names.
	my $rrcnt = 0;			# Number of rollrecs we split.

# print "rollrec_split:  down in\n";

	#
	# Make sure a set of rollrec files was specified.
	#
	if((! defined($newrrf)) || ($newrrf eq ''))
	{
		err("no target rollrec file given for split\n",1);
		return(-1);
	}

	#
	# Make sure a set of rollrec files was specified.
	#
	if(@rrlist == 0)
	{
		err("no rollrec names given for split\n",1);
		return(-2);
	}

	#
	# Count the valid rollrec names in the name list.
	#
	foreach my $rrn (@rrlist)
	{
		$valid++ if(defined($rollrecs{$rrn}));
	}

	#
	# Ensure that at least one of the rollrec names in the name list
	# is valid.
	#
	if($valid == 0)
	{
		err("no rollrec names given for split are existing rollrecs\n",1);
		return(-3);
	}

	#
	# Open the target rollrec file for appending.
	#
	if(open(ROLLREC_SPLIT,">> $newrrf") == 0)
	{
		err("unable to open \"$newrrf\" for split\n",1);
		return(-4);
	}

	#
	# Read each remaining rollrec file and add it to our internal
	# rollrec collection.
	#
	foreach my $rrn (@rrlist)
	{
		my $rrind;			# Index to rollrec's first line.

		#
		# If this name isn't the name of an existing rollrec, we'll
		# save the name and go to the next.
		#
		if(! exists($rollrecs{$rrn}))
		{
			push @badnames,$rrn;
			next;
		}

		#
		# Find this rollrec in @rollreclines.
		#
		for($rrind=0;$rrind<$rollreclen;$rrind++)
		{
			my $ln = $rollreclines[$rrind];	# Line in rollrec file.
			my $rrname;			# Rollrec name.

			#
			# Dig out the line's keyword and value.
			#
			$ln =~ /^\s*(roll|skip)\s+"([a-z0-9\/\-+_.,: \t]+)"/i;
			$rrname = $2;

			#
			# If this line has the rollrec's name and is the start
			# of a new rollrec, then we've found our man.
			#
			#
			last if(lc($rrname) eq lc($rrn));
		}

		#
		# Bump our count of split rollrecs.
		#
		$rrcnt++;

		#
		# Find the specified field's entry in the rollrec's lines in
		# @rollreclines.  We'll skip over lines that don't have a
		# keyword and dquotes-enclosed value.
		#
		print ROLLREC_SPLIT "$rollreclines[$rrind]";
		for($rrind++; $rrind<$rollreclen; $rrind++)
		{
			my $ln = $rollreclines[$rrind];	# Line in rollrec file.
			my $lkw;			# Line's keyword.

			#
			# Get the line's keyword and value.
			#
			$ln =~ /^\s*([a-z_]+)\s+"([a-z0-9\/\-+_.,: \t]*)"/i;
			$lkw = $1;

			#
			# If we hit the beginning of the next rollrec or a
			# blank line, we'll write a blank line and, drop out.
			#
			if(($lkw =~ /^(roll|skip)$/i) || ($ln eq "\n"))
			{
				print ROLLREC_SPLIT "\n";
				last;
			}

			print ROLLREC_SPLIT "$ln";
		}

		#
		# Delete the named rollrec.
		#
		rollrec_del($rrn);
	}

	#
	# Close the file handle.
	#
	close(ROLLREC_SPLIT);

	#
	# If we found some names that aren't in the original rollrec file,
	# we'll give an error and return the list of bad names.
	#
	if(@badnames > 0)
	{
		err("invalid rollrec names (@badnames) in split\n",1);
		return(-5, @badnames);
	}

	#
	# Write the updated rollrec file.
	#
	$modified = 1;
	rollrec_write();

	#
	# Return the number of rollrecs we split into a new rollrec file.
	#
	return($rrcnt);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_readfile()
#
# Purpose:	Read the specified rollrec file.  The contents are read into
#		the @rollreclines array and the rollrecs are broken out into
#		the %rollrecs hash table.
#
sub rollrec_readfile
{
	my $rfh = shift;			# File handle for rollrec file.
	my $name;				# Name of the rollrec entry.
	my $havecmdsalready = 0;

	#
	# If we already have commands loaded, don't reload them.
	#
	my @currentcmds = rollmgr_getallqueuedcmds();
	$havecmdsalready = 1 if ($#currentcmds > -1);

	#
	# Grab the lines and pop 'em into the rollreclines array.  We'll also
	# save each rollrec into a hash table for easy reference.
	#
	while(<$rfh>)
	{
		my $line;		# Line from the rollrec file.
		my $keyword = "";	# Keyword from the line.
		my $value = "";		# Keyword's value.

		$line = $_;

		#
		# Save the line in our array of rollrec lines.
		#
		$rollreclines[$rollreclen] = $line;
		$rollreclen++;

		#
		# Skip comment lines and empty lines.
		#
		if(($line =~ /^\s*$/) || ($line =~ /^\s*[;#]/))
		{
			next;
		}

		#
		# Grab the keyword and value from the line.  The keyword
		# must be alphabetic.  The value can contain alphanumerics,
		# and a number of punctuation characters.  The value *must*
		# be enclosed in double quotes.
		#
		$line =~ /^\s*([a-zA-Z_]+)\s+"([a-zA-Z0-9\/\-+_.,: \@\t]*)"/;
		$keyword = $1;
		$value = $2;
#		print "rollrec_readfile:  keyword <$keyword>\t\t<$value>\n";

		#
		# If the keyword is "roll", then we're starting a new record.
		# We'll save the name of the rollrec, and then proceed on to
		# the next line.  
		#
		if(($keyword =~ /^roll$/i) || ($keyword =~ /^skip$/i))
		{
			$name = $value;

			#
			# If this name has already been used for a rollrec,
			# we'll whinge, clean up, and return.  No rollrecs
			# will be retained.
			#
			if(exists($rollrecs{$name}))
			{
				rollrec_discard();
				err("rollrec_readfile:  duplicate record name; aborting...\n",-1);
				return(-1);
			}
			rollrec_newrec($keyword,$name);
			next;
		}

		if($keyword =~ /^cmd$/i)
		{
			#
			# The line is used to issue a specific command to run.
			# We queue it for later processing.
			#
print STDERR "rollrec_readfile:  processing command: $value / $havecmdsalready\n";

			next if($havecmdsalready);

			my $cmdtoload = $value;
			my ($cmd, $arg) = ($cmdtoload =~ /^\s*(\w+)\s*(.*)$/);
			$cmd = "rollcmd_" . $cmd if($cmd !~ /^rollcmd_/);
 

			if(rollmgr_verifycmd($cmd) == 0)
			{
				err("rollrec_readfile: invalid command $cmdtoload\n", -1);
				next;
			}

			#
			# Save the command in our queue to process.
			#
			rollmgr_queuecmd($cmd, $arg);

			#
			# Remove the line from the file to write back out.
			#
			pop @rollreclines;
			next;
		}

		#
		# Save this subfield into the rollrec's collection.
		#
		$rollrecs{$name}{$keyword} = $value;
	}

	return(0);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_names()
#
# Purpose:	Smoosh the rollrec names into an array and return the array.
#
sub rollrec_names
{
	my $rrn;				# Rollrec name index.
	my @names = ();				# Array for rollrec names.

# print "rollrec_names:  down in\n";

	foreach $rrn (sort(keys(%rollrecs)))
	{
		push @names, $rrn;
	}

	return(@names);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_exists()
#
# Purpose:	Return a flag indicating if the given rollrec exists.
#
sub rollrec_exists
{
	my $name = shift;

# print "rollrec_exists:  down in ($name)\n";

	return(exists($rollrecs{$name}));
}

#--------------------------------------------------------------------------
# Routine:	rollrec_fullrec()
#
# Purpose:	Return all entries in a given rollrec.
#
sub rollrec_fullrec
{
	my $name = shift;
	my $nrec = $rollrecs{$name};

# print "rollrec_fullrec:  down in ($name)\n";

	return($nrec);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_recval()
#
# Purpose:	Return the value of a name/subfield pair.
#
sub rollrec_recval
{
	my $name = shift;
	my $field = shift;
	my $val = $rollrecs{$name}{$field};

# print "rollrec_recval:  down in ($name) ($field) ($val)\n";

	return($val);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_rectype()
#
# Purpose:	Change the value of a rollrec.  The new value may only be
#		"roll" or "skip".
#
# Return Values:
#		1 - success
#		0 - failure (invalid record type or rollrec not found)
#
sub rollrec_rectype
{
	my $name    = shift;		# Name of rollrec we're modifying.
	my $rectype = shift;		# Rollrec's new type.

# print STDERR "rollrec_rectype:  <$name> <$rectype>\n";

	#
	# Make sure we've got a valid record type.
	#
	return(0) if(($rectype ne "roll") && ($rectype ne "skip"));

	#
	# Find the appropriate entry to modify in @rollreclines.  If the
	# given field isn't set in $name's rollrec, we'll insert this as
	# a new field at the end of that rollrec.
	#
	for(my $rrind=0;$rrind<$rollreclen;$rrind++)
	{
		my $line = $rollreclines[$rrind];	# Line in rollrec file.
		my $rtype;				# Rollrec type.
		my $rrname;				# Rollrec name.

		#
		# Dig out the line's keyword and value.
		#
		$line =~ /^\s*(roll|skip)\s+"([a-zA-Z0-9\/\-+_.,: \t]+)"/i;
		$rtype = $1;
		$rrname = $2;

		#
		# If this line has the rollrec's name and is the start of a
		# new rollrec, then like the Mounties we've found our man.
		# We'll change the record type and return success.
		#
		if(lc($rrname) eq lc($name))
		{
			$rollrecs{$name}{'rollrec_type'} = $rectype;
			$line =~ s/$rtype/$rectype/;
			$rollreclines[$rrind] = $line;
			$modified = 1;
			return(1);
		}
	}

	#
	# We didn't find the line, so we'll return failure.
	#
	return(0);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_setval()
#
# Purpose:	Set the value of a name/subfield pair.  The value is saved
#		in both %rollrecs and in @rollreclines.  The $modified file-
#		modified flag is updated, along with the length $rollreclen.
#
sub rollrec_setval
{
	my $name   = shift;		# Name of rollrec we're modifying.
	my $field  = shift;		# Rollrec's subfield to be changed.
	my $val	   = shift;		# New value for the rollrec's subfield.

	my $found = 0;			# Rollrec-found flag.
	my $fldind;			# Loop index.
	my $rrind;			# Loop index for finding rollrec.
	my $lastfld = 0;		# Last found field in @rollreclines.

# print "rollrec_setval:  down in\n";

	#
	# If a rollrec of the specified name doesn't exist, we'll create a
	# new one.  We'll add it to @rollreclines and %rollrecs.
	#
	# We'll also assume it's a "roll" type rollrec.
	#
	if(!exists($rollrecs{$name}))
	{
		#
		# Add the rollrec to the %rollrecs hash.
		#
		rollrec_newrec("roll",$name);

		#
		# Start the new rollrec in @rollreclines.
		#
		$rollreclines[$rollreclen] = "\n";
		$rollreclen++;
		$rollreclines[$rollreclen] = "roll\t\"$name\"\n";
		$rollreclen++;
	}

	#
	# Set the new value for the name/field in %rollrecs.
	#
	$rollrecs{$name}{$field} = $val;

	#
	# Find the appropriate rollrec to modify in @rollreclines.  If the
	# given field isn't set in $name's rollrec, we'll insert this as
	# a new field at the end of that rollrec.
	#
	for($rrind=0;$rrind<$rollreclen;$rrind++)
	{
		my $line = $rollreclines[$rrind];	# Line in rollrec file.
		my $rrname;				# Rollrec name.

		#
		# Dig out the line's keyword and value.
		#
		$line =~ /^\s*(roll|skip)\s+"([a-zA-Z0-9\/\-+_.,: \t]+)"/i;
		$rrname = $2;

		#
		# If this line has the rollrec's name and is the start of a
		# new rollrec, then we've found our man.
		#
		# IMPORTANT NOTE:  We will *always* find the rollrec we're
		#		   looking for.  The exists() check above
		#		   ensures that there will be a rollrec with
		#		   the name we want.
		#
		last if(lc($rrname) eq lc($name));
	}

	#
	# Find the specified field's entry in the rollrec's lines in
	# @rollreclines.  We'll skip over lines that don't have a keyword
	# and dquotes-enclosed value.  If we hit the next rollrec then we'll
	# stop looking and add a new entry at the end of the rollrec's fields.
	#
	for($fldind=$rrind+1;$fldind<$rollreclen;$fldind++)
	{
		my $line = $rollreclines[$fldind];	# Line in rollrec file.
		my $lkw;				# Line's keyword.
		my $lval;				# Line's value.

		#
		# Get the line's keyword and value.
		#
		$line =~ /^\s*([a-zA-Z_]+)\s+"([a-zA-Z0-9\/\-+_.,: \t]*)"/;
		$lkw = $1;
		$lval = $2;

		#
		# Skip lines that don't match the expected field/value pattern.
		#
		next if($lkw eq "");

		#
		# If we hit the beginning of the next rollrec without
		# finding the field, drop out and insert it.
		#
		last if((lc($lkw) eq "roll") || (lc($lkw) eq "skip"));

		#
		# Save the index of the last field we've looked at that
		# belongs to the rollrec.
		#
		$lastfld = $fldind;

		#
		# If we found the field, set the found flag, drop out and
		# modify it.
		#
		if(lc($lkw) eq lc($field))
		{
			$found = 1;
			last;
		}
	}

	#
	# If we found the entry, we'll modify it in place.
	# If we didn't find the entry, we'll insert a new line into the array.
	#
	if($found)
	{
		$rollreclines[$fldind] =~ s/"([a-zA-Z0-9\/\-+_.,: \t]*)"/"$val"/;
	}
	else
	{
		my $newline = "\t$field\t\t\"$val\"\n";

		#
		# If the keyword is longer than 7 characters, we'll lop out one
		# of the tabs between the keyword and the value.  This is to do
		# some rough, simple formatting to keep the rollrec file some-
		# what orderly.  This assumes eight-character tabstops.
		#
		if(length($field) > 7)
		{
			$newline =~ s/\t\t/\t/;
		}

		#
		# If the starting rollrec line is the last line in the file,
		# we'll just push the new line at the end.  If it's somewhere
		# in the middle, we'll do the magic to insert it at the start
		# of the rollrec.
		#
		my @endarr = splice(@rollreclines,$rrind+1);
		push(@rollreclines,$newline);
		push(@rollreclines,@endarr);

		#
		# Bump the array length counter.
		#
		$rollreclen++;
	}

	#
	# Tell the world (or at least the module) that the file has
	# been modified.
	#
	$modified = 1;
	return(0);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_delfield()
#
# Purpose:	Delete a name/subfield pair.  The value is deleted from
#		both %rollrecs and @rollreclines.  The $modified file-
#		modified flag is updated, along with the length $rollreclen.
#
sub rollrec_delfield
{
	my $name   = shift;		# Name of rollrec we're modifying.
	my $field  = shift;		# Rollrec's subfield to be deleted.

	my $found = 0;			# Rollrec-found flag.
	my $fldind;			# Loop index.
	my $rrind;			# Loop index for finding rollrec.
	my $lastfld = 0;		# Last found field in @rollreclines.

# print "rollrec_delfield:  down in\n";

	#
	# Return if a rollrec of the specified name doesn't exist.
	#
	return(0) if(!exists($rollrecs{$name}));

	#
	# Return if a rollrec of the specified name doesn't have the
	# specified field.
	#
	return(0) if(!exists($rollrecs{$name}{$field}));

	#
	# Delete the field from %rollrecs.
	#
	delete($rollrecs{$name}{$field});

	#
	# Find the appropriate rollrec to modify in @rollreclines.
	#
	for($rrind=0;$rrind<$rollreclen;$rrind++)
	{
		my $line = $rollreclines[$rrind];	# Line in rollrec file.
		my $rrname;				# Rollrec name.

		#
		# Dig out the line's keyword and value.
		#
		$line =~ /^\s*(roll|skip)\s+"([a-zA-Z0-9\/\-+_.,: \t]+)"/i;
		$rrname = $2;

		#
		# If this line has the rollrec's name and is the start of a
		# new rollrec, then we've found our man.
		#
		# IMPORTANT NOTE:  We will *always* find the rollrec we're
		#		   looking for.  The exists() check above
		#		   ensures that there will be a rollrec with
		#		   the name we want.
		#
		last if(lc($rrname) eq lc($name));
	}

	#
	# Find the specified field's entry in the rollrec's lines in
	# @rollreclines.  We'll skip over lines that don't have a keyword
	# and dquotes-enclosed value.
	#
	for($fldind=$rrind+1;$fldind<$rollreclen;$fldind++)
	{
		my $line = $rollreclines[$fldind];	# Line in rollrec file.
		my $lkw;				# Line's keyword.
		my $lval;				# Line's value.

		#
		# Get the line's keyword and value.
		#
		$line =~ /^\s*([a-zA-Z_]+)\s+"([a-zA-Z0-9\/\-+_.,: \t]*)"/;
		$lkw = $1;
		$lval = $2;

		#
		# Go to the next line if this one doesn't match the
		# field/value pattern.
		#
		next if($lkw eq "");

		#
		# If we hit the beginning of the next rollrec without
		# finding the field, drop out.
		#
		last if((lc($lkw) eq "roll") || (lc($lkw) eq "skip"));

		#
		# Save the index of the last field we've looked at that
		# belongs to the rollrec.
		#
		$lastfld = $fldind;

		#
		# If we found the field, set the found flag, drop out and
		# modify it.
		#
		if(lc($lkw) eq lc($field))
		{
			$found = 1;
			last;
		}
	}

	#
	# If we found the entry, we'll delete it.
	#
	if($found)
	{
		splice @rollreclines, $fldind, 1;
	}

	#
	# Tell the world (or at least the module) that the file has
	# been modified.
	#
	$modified = 1;
	return(1);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_add()
#
# Purpose:	Adds a new rollrec and fields to %rollrecs and $rollreclines.
#
sub rollrec_add
{
	my $rrtype = shift;		# Rollrec type.
	my $rrname = shift;		# Name of rollrec we're creating.
	my $flds   = shift;		# Reference to rollrec fields.

	my $chronosecs;			# Current time in seconds.
	my $chronostr;			# Current time string.


	my %fields;			# Rollrec fields.

# print "rollrec_add:  down in\n";

	#
	# Get the timestamp.
	#
	$chronosecs = time();
	$chronostr  = gmtime($chronosecs);

	#
	# Create the basic rollrec info.
	#
	rollrec_newrec($rrtype,$rrname);

	#
	# Add the new rollrec's first line to the end of the rollrec table.
	#
	$rollreclines[$rollreclen] = "\n";
	$rollreclen++;
	$rollreclines[$rollreclen] = "roll\t\"$rrname\"\n";
	$rollreclen++;

	#
	# Fill the new rollrec with the caller's hash fields and add it to
	# the end of the rollrec table.
	#
	if(defined($flds))
	{
		%fields = %$flds;
		foreach my $fn (@ROLLFIELDS)
		{
			my $spacing = "\t\t";	# Spacing string.

			#
			# Only add the timestamp at the end, and only
			# add the timestamp we're going to put in.
			#
			if(($fn eq 'rollrec_rollsecs') || ($fn eq 'rollrec_rolldate'))
			{
				next;
			}

			#
			# If this field isn't defined for the rollrec,
			# don't add it in.
			#
			if(!defined($fields{$fn}))
			{
				next;
			}

			#
			# Drop back to a single tab between key and value
			# if the key name is long.
			#
			$spacing = "\t"    if(length($fn) > 7);

			#
			# Add the field to the hash table and to the rollrec
			# file contents array.
			#
			$rollrecs{$rrname}{$fn} = $fields{$fn};
			$rollreclines[$rollreclen] = "\t$fn$spacing\"$fields{$fn}\"\n";
			$rollreclen++;
		}
	}

	#
	# Set a timestamp for this entry.
	#
	$rollrecs{$rrname}{'rollrec_rollsecs'} = $chronosecs;
	$rollrecs{$rrname}{'rollrec_rolldate'} = $chronostr;
	$rollreclines[$rollreclen] = "\trollrec_rollsecs\t\"$chronosecs\"\n";
	$rollreclen++;
	$rollreclines[$rollreclen] = "\trollrec_rolldate\t\"$chronostr\"\n";
	$rollreclen++;

	#
	# Put a blank line after the final line of the rollrec.
	#
	$rollreclines[$rollreclen] = "\n";
	$rollreclen++;

	#
	# Mark the rollrec file as having been modified.
	#
	$modified = 1;
	return(0);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_del()
#
# Purpose:	Deletes a rollrec and fields from %rollrecs and $rollreclines.
#
sub rollrec_del
{
	my $rrname = shift;		# Name of rollrec we're deleting.

	my %rollrec;			# Rollrec to be deleted.
	my $rrr;			# Rollrec reference.

	my $ind;			# Index into rollreclines.
	my $rrind;			# Index to rollrec's first line.
	my $line;			# Rollrec line from @rollreclines.
	my $lkey;			# Rollrec line's key.
	my $lval;			# Rollrec line's value.
	my $len;			# Length of array slice to delete.

# print "rollrec_del:  down in\n";

	#
	# Don't allow empty rollrec names.
	#
	return(-1) if($rrname eq "");

	#
	# Get a copy of the rollrec from the rollrec hash and then delete
	# the original.
	#
	$rrr = $rollrecs{$rrname};
	%rollrec = %$rrr;
	delete $rollrecs{$rrname};

	#
	# Find the index of the first line for this rollrec in the
	# list of file lines.
	#
	for($ind = 0;$ind < $rollreclen; $ind++)
	{
		$line = $rollreclines[$ind];

		$line =~ /\s*(\S+)\s+(\S+)/;
		$lkey = $1;
		$lval = $2;

		$lval =~ s/"//g;

		last if($lval eq $rrname);
	}
	$rrind = $ind;

	#
	# If we didn't find a rollrec with this name, return failure.
	#
	return(-1) if($ind == $rollreclen);

	#
	# Find the beginning of the next rollrec.
	#
	for($ind = $rrind+1;$ind < $rollreclen; $ind++)
	{
		$line = $rollreclines[$ind];

		$line =~ /\s*(\S+)\s+(\S+)/;
		$lkey = $1;
		$lval = $2;

		last if($lkey eq "roll");
	}
	$ind--;

	#
	# Find the end of the previous rollrec (the one to be deleted.)
	#
	while($ind > $rrind)
	{
		last if($rollreclines[$ind] ne "\n");
		$ind--;
	}

	#
	# Delete the rollrec from @rollreclines.
	#
	$len = $ind - $rrind + 1;
	splice(@rollreclines,$rrind,$len);
	$rollreclen -= $len;

	#
	# Mark that the file has been modified.
	#
	$modified = 1;
	return(0);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_rename()
#
# Purpose:	Renames a rollrec.
#
sub rollrec_rename
{
	my $oldname = shift;		# Name of rollrec we're renaming.
	my $newname = shift;		# New name of rollrec.

	my %rollrec;			# Rollrec to be deleted.
	my $rrr;			# Rollrec reference.

	my $ind;			# Index into rollreclines.
	my $rrind;			# Index to rollrec's first line.
	my $line;			# Rollrec line from @rollreclines.
	my $lkey;			# Rollrec line's key.
	my $lval;			# Rollrec line's value.
	my $len;			# Length of array slice to delete.

# print "rollrec_rename:  down in\n";

	#
	# Don't allow empty rollrec names.
	#
	return(-1) if($oldname eq '');
	return(-2) if($newname eq '');

	#
	# The old rollrec must exist.
	#
	return(-3) if(! exists($rollrecs{$oldname}));

	#
	# Don't allow renames to existing rollrec names.
	#
	return(-4) if(exists($rollrecs{$newname}));

	#
	# Get a copy of the old rollrec.
	#
	$rollrecs{$newname} = $rollrecs{$oldname};

	#
	# Change the name field in the new rollrec.
	#
	$rollrecs{$newname}{'rollrec_name'} = $newname;
	$rrr = $rollrecs{$newname};
	%rollrec = %$rrr;

	#
	# Find the index of the first line for this rollrec in the
	# list of file lines.
	#
	$rollreclen = @rollreclines;
	for($rrind = 0;$rrind < $rollreclen; $rrind++)
	{
		$line = $rollreclines[$rrind];

		$line =~ /^\s*([a-zA-Z_]+)\s+"([a-zA-Z0-9\/\-+_.,: \@\t]*)"/;
		$lkey = $1;
		$lval = $2;

		$lval =~ s/"//g;

		last if($lval eq $oldname);
	}

	#
	# If we didn't find a rollrec with this name, return failure.
	#
	return(-5) if($rrind == $rollreclen);

	#
	# Find the beginning of the next rollrec.
	#
	for($ind = $rrind+1;$ind < $rollreclen; $ind++)
	{
		$line = $rollreclines[$ind];

		$line =~ /^\s*([a-zA-Z_]+)\s+"([a-zA-Z0-9\/\-+_.,: \@\t]*)"/;
		$lkey = $1;
		$lval = $2;

		last if($lkey eq "roll");
	}
	$ind--;

	#
	# Find the end of the previous rollrec (the one to be deleted.)
	#
	while($ind > $rrind)
	{
		last if($rollreclines[$ind] ne "\n");
		$ind--;
	}

	#
	# Find the rollrec's roll or skip line and change the name.
	#
	for(my $i=$rrind; $i <= $ind; $i++)
	{
		my $chunk;			# Key and spacing from line.

		$rollreclines[$i] =~ /^(\s*([a-zA-Z_]+)\s+)"([a-zA-Z0-9\/\-+_.,: \@\t]*)"/;
		$chunk = $1;
		$lkey = $2;

		if(($lkey eq 'roll') || ($lkey eq 'skip'))
		{
			$rollreclines[$i] = "$chunk\"$newname\"\n";
			last;
		}
	}

	#
	# Delete the old rollrec.
	#
	delete $rollrecs{$oldname};

	#
	# Mark that the file has been modified.
	#
	$modified = 1;
	return(0);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_settime()
#
# Purpose:	Sets the phase-start time in the rollrec.
#
sub rollrec_settime
{
	my $cnt	 = @_;			# Number of arguments.
	my $name = shift;		# Name of rollrec we're creating.
	my $val  = shift;		# Optional argument.

	my $chronos;			# Timestamp for the record.

# print "rollrec_settime:  down in\n";

	if(($cnt == 2) && ($val == 0))
	{
		$chronos = "";
	}
	else
	{
		$chronos = gmtime();
		$chronos =~ s/\n$//;
	}

	rollrec_setval($name,"phasestart",$chronos);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_newrec()
#
# Purpose:	Creates a rollrec in %rollrecs.  The name and type fields
#		are set.
#
sub rollrec_newrec
{
	my $type = shift;		# Type of rollrec we're creating.
	my $name = shift;		# Name of rollrec we're creating.

# print "rollrec_newrec:  down in\n";

	return if(($type ne "roll") && ($type ne "skip"));

	$rollrecs{$name}{"rollrec_name"} = $name;
	$rollrecs{$name}{"rollrec_type"} = $type;
}

#--------------------------------------------------------------------------
# Routine:	rollrec_fields()
#
# Purpose:	Return the list of rollrec fields.
#
sub rollrec_fields
{
# print "rollrec_fields:  down in\n";

	return(@ROLLFIELDS);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_default()
#
# Purpose:	Return the default rollrec file.
#
sub rollrec_default
{
	my $confdir;				# Configuration directory.
	my $defrr;				# Default rollrec name.

# print "rollrec_default:  down in\n";

	#
	# Get the DNSSEC-Tools config directory.
	#
	$confdir = getconfdir() || $DEFAULT_DNSSECTOOLS_DIR;

	#
	# Build our lock file.
	#
	$defrr = "$confdir/$DEFAULT_ROLLREC";

	return($defrr);
}


#--------------------------------------------------------------------------
# Routine:	rollrec_init()
#
# Purpose:	Initialize the internal data.
#
sub rollrec_init
{
# print "rollrec_init:  down in\n";

	%rollrecs     = ();
	@rollreclines = ();
	$rollreclen   = 0;
	$modified     = 0;
}

#--------------------------------------------------------------------------
# Routine:	rollrec_discard()
#
# Purpose:	Discard the current rollrec file -- don't save the contents,
#		don't delete the file, reset all internal fields.
#
sub rollrec_discard
{
# print "rollrec_discard:  down in\n";

	close(ROLLREC);
	rollrec_init();
}

#--------------------------------------------------------------------------
# Routine:	rollrec_close()
#
# Purpose:	Save the roll record file and close the descriptor.
#
sub rollrec_close
{
# print "rollrec_close:  down in\n";

	rollrec_write();
	close(ROLLREC);
}

#--------------------------------------------------------------------------
# Routine:	rollrec_write()
#
# Purpose:	Save the roll record file and leave the file handle open.
#		We'll get an exclusive lock on the rollrec file in order
#		to (try to) ensure we're the only ones writing the file.
#
#		We'll make a (hopefully atomic) copy of the in-core rollrec
#		lines prior to trying to write.  This is an attempt to
#		keep the data from being mucked with while we're using it.
#
sub rollrec_write
{
	my $writecmds = shift;	# Boolean for saving unexecuted commands.
	my $rrc = "";		# Concatenated rollrec file contents.
	my $ofh;		# Old file handle.

	my @rrlines = @rollreclines;	# Copy of The Rollrec.
	my $rrlen;			# Number of lines in The Rollrec.

# print STDERR "rollrec_write:  down in\n";

	my @currentcmds = rollmgr_getallqueuedcmds();

	#
	# If the file hasn't changed, we'll skip writing.
	#
	return if(!$modified && ($#currentcmds == -1));

	#
	# Make sure we've got the correct count of rollrec lines.
	#
	$rrlen = @rrlines;

	#
	# Loop through the array of rollrec lines and concatenate them all.
	#
	for(my $ind = 0; $ind < $rrlen; $ind++)
	{
		$rrc .= $rrlines[$ind];
	}

	#
	# Remember any unprocessed queue commands.
	#
	if($writecmds)
	{
		foreach my $cmdandvalue (rollmgr_getallqueuedcmds())
		{
			my ($cmd, $value) = @$cmdandvalue;
			$cmd =~ s/^rollcmd_//;
			$rrc .= "cmd \"$cmd $value\"\n";
		}
	}

	#
	# Lock the rollrec file.
	#
	flock(ROLLREC,LOCK_EX);

	#
	# Force immediate writes of ROLLREC.
	#
	$ofh = select ROLLREC;
	$| = 1;

	#
	# Zap the rollrec file and write out the new one.
	#
	seek(ROLLREC,0,0);
	truncate(ROLLREC,0);
	print ROLLREC $rrc;

	#
	# Reset ROLLREC buffering to original state.
	#
	select $ofh;

	#
	# Unlock the rollrec file.
	#
	return(flock(ROLLREC,LOCK_UN));
}

#--------------------------------------------------------------------------
# Routine:	rollrec_dump_hash()
#
# Purpose:	Dump the parsed rollrec entries.
#
sub rollrec_dump_hash
{
# print "rollrec_dump_hash:  down in\n";

	#
	# Loop through the hash of rollrecs and print the rollrec names,
	# subfields, and values.
	#
	foreach my $k (sort(keys(%rollrecs)))
	{
		print "rollrec - $k\n";
		my $subp = $rollrecs{$k};
		my %subrecs = %$subp;
		foreach my $sk (sort(keys(%subrecs)))
		{
			print "\t$sk\t\t$subrecs{$sk}\n";
		}
		print "\n";
	}
}

#--------------------------------------------------------------------------
# Routine:	rollrec_dump_array()
#
# Purpose:	Display the contents of @rollreclines.
#
sub rollrec_dump_array
{
# print "rollrec_dump_array:  down in\n";

	#
	# Loop through the array of rollrec lines and print them all.
	#
	for(my $ind=0;$ind<$rollreclen;$ind++)
	{
		print $rollreclines[$ind];
	}
}

1;

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

=pod

=head1 NAME

Net::DNS::SEC::Tools::rollrec - Manipulate a DNSSEC-Tools rollrec file.

=head1 SYNOPSIS

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

  rollrec_lock();
  rollrec_read("localhost.rollrec");

  @rrnames = rollrec_names();

  $flag = rollrec_exists("example.com");

  $rrec = rollrec_fullrec("example.com");
  %rrhash = %$rrec;
  $zname = $rrhash{"maxttl"};

  $val = rollrec_recval("example.com","zonefile");

  rollrec_add("roll","example.com",\%rollfields);
  rollrec_add("skip","example.com",\%rollfields);

  rollrec_del("example.com");

  rollrec_rename("example.com","subdom.example.com");

  rollrec_type("example.com","roll");
  rollrec_type("example.com","skip");

  rollrec_setval("example.com","zonefile","db.example.com");

  rollrec_delfield("example.com","directory");

  rollrec_settime("example.com");
  rollrec_settime("example.com",0);

  @rollrecfields = rollrec_fields();

  $default_file = rollrec_default();

  $count = rollrec_merge("primary.rrf", "new0.rrf", "new1".rrf");
  @retvals = rollrec_split("new-rollrec.rrf", @rollrec_list);

  rollrec_write();
  rollrec_close();
  rollrec_discard();

  rollrec_unlock();

=head1 DESCRIPTION

The B<Net::DNS::SEC::Tools::rollrec> module manipulates the contents of a
DNSSEC-Tools I<rollrec> file.  I<rollrec> files describe the status of a
zone rollover process, as performed by the DNSSEC-Tools programs.  Module
interfaces exist for looking up I<rollrec> records, creating new records,
and modifying existing records.

A I<rollrec> file is organized in sets of I<rollrec> records.  I<rollrec>s
describe the state of a rollover operation.  A I<rollrec> consists of a set
of keyword/value entries.  The following is an example of a I<rollrec>:

    roll "example"
	zonename		"example.com"
	zonefile		"/etc/dnssec-tools/zones/db.example.com"
	keyrec			"/etc/dnssec-tools/keyrec/example.keyrec"
	directory		"/etc/dnssec-tools/dir-example.com"
	kskphase		"0"
	zskphase		"2"
	maxttl			"86400"
	administrator		"bob@bobhost.example.com"
	phasestart		"Wed Mar 09 21:49:22 2005"
	display			"0"
	loglevel		"info"
	rollrec_rollsecs	"1115923362"
	rollrec_rolldate	"Tue Mar 09 19:12:54 2005"
	curerrors		0
	maxerrors		5
        # optional records:
        istrustanchor           "yes"
        holddowntime            "8W"

Additionally, commands to be acted upon at start-up can be defined using the
"cmd" token as shown in the following example.

    cmd "rollzsk example.com"

Use this feature with caution and only if you understand the internals of
B<rollerd> and I<exactly> what will be done by the specified command.

The first step in using this module must be to read the I<rollrec> file.  The
I<rollrec_read()> interface reads the file and parses it into an internal
format.  The file's records are copied into a hash table (for easy reference
by the B<rollrec.pm> routines) and in an array (for
preserving formatting and comments.)

After the file has been read, the contents are referenced using
I<rollrec_fullrec()> and I<rollrec_recval()>.  The I<rollrec_add()>,
I<rollrec_setval()>, and I<rollrec_settime()> interfaces are used
to modify the contents of a {\it rollrec} record.

If the I<rollrec> file has been modified, it must be explicitly written or
the changes will not saved.  I<rollrec_write()> saves the new contents to disk.
I<rollrec_close()> saves the file and close the Perl file handle to the
I<rollrec> file.  If a I<rollrec> file is no longer wanted to be open, yet
the contents should not be saved, I<rollrec_discard()> gets rid of the data
closes and the file handle B<without> saving any modified data.

=head1 ROLLREC LOCKING

This module includes interfaces for synchronizing access to the I<rollrec>
files.  This synchronization is very simple and relies upon locking and
unlocking a single lock file for all I<rollrec> files.

I<rollrec> locking is not required before using this module, but it is
recommended.  The expected use of these facilities follows:

    rollrec_lock() || die "unable to lock rollrec file\n";
    rollrec_read();
    ... perform other rollrec operations ...
    rollrec_close();
    rollrec_unlock();

Synchronization is performed in this manner due to the way the module's
functionality is implemented, as well as providing flexibility to users
of the module.  It also provides a clear delineation in callers' code as
to where and when I<rollrec> locking is performed.

This synchronization method has the disadvantage of having a single lockfile
as a bottleneck to all I<rollrec> file access.  However, it reduces complexity
in the locking interfaces and cuts back on the potential number of required
lockfiles.

Using a single synchronization file may not be practical in large
installations.  If that is found to be the case, then this will be reworked.

=head1 ROLLREC INTERFACES

The interfaces to the B<rollrec.pm> module are given below.

=over 4

=item I<rollrec_add(rollrec_type,rollrec_name,fields)>

This routine adds a new I<rollrec> to the I<rollrec> file and the internal
representation of the file contents.  The I<rollrec> is added to both the
I<%rollrecs> hash table and the I<@rollreclines> array.  Entries are only
added if they are defined for I<rollrec>s.

I<rollrec_type> is the type of the I<rollrec>.  This must be either "roll"
or "skip".  I<rollrec_name> is the name of the I<rollrec>.  I<fields> is a
reference to a hash table that contains the name/value I<rollrec> fields.  The
keys of the hash table are always converted to lowercase, but the entry values
are left as given.

Timestamp fields are added at the end of the I<rollrec>.  These fields have
the key values I<rollrec_gensecs> and I<rollrec_gendate>.

A blank line is added after the final line of the new I<rollrec>.
The I<rollrec> file is not written after I<rollrec_add()>, though
it is internally marked as having been modified.

=item I<rollrec_del(rollrec_name)>

This routine deletes a I<rollrec> from the I<rollrec> file and the internal
representation of the file contents.  The I<rollrec> is deleted from both
the I<%rollrecs> hash table and the I<@rollreclines> array.

Only the I<rollrec> itself is deleted from the file.  Any associated comments
and blank lines surrounding it are left intact.  The I<rollrec> file is not
written after I<rollrec_del()>, though it is internally marked as having been
modified.

Return values are:

     0 successful rollrec deletion
    -1 unknown name

=item I<rollrec_close()>

This interface saves the internal version of the I<rollrec> file (opened with
I<rollrec_read()>) and closes the file handle. 

=item I<rollrec_delfield(rollrec_name,field)>

Deletes the given field from the specified I<rollrec>.  The file is
B<not> written after updating the value, but the internal file-modified flag
is set.  The value is saved in both I<%rollrecs> and in I<@rollreclines>.

Return values:

    0 - failure (rollrec not found or rollrec does not
	contain the field)
    1 - success

=item I<rollrec_discard()>

This routine removes a I<rollrec> file from use by a program.  The internally
stored data are deleted and the I<rollrec> file handle is closed.  However,
modified data are not saved prior to closing the file handle.  Thus, modified
and new data will be lost.

=item I<rollrec_exists(rollrec_name)>

This routine returns a boolean flag indicating if the I<rollrec> named in
I<rollrec_name> exists.

=item I<rollrec_fullrec(rollrec_name)>

I<rollrec_fullrec()> returns a reference to the I<rollrec> specified in
I<rollrec_name>.

=item I<rollrec_lock()>

I<rollrec_lock()> locks the I<rollrec> lockfile.  An exclusive lock is
requested, so the execution will suspend until the lock is available.  If the
I<rollrec> synchronization file does not exist, it will be created.  If the
process can't create the synchronization file, an error will be returned.
Success or failure is returned.

=item I<rollrec_merge(target_rollrec_file,rollrec_file2, ... rollrec_fileN)>

This interface merges the specified I<rollrec> files.  It reads each file
and parses them into a I<rollrec> hash table and a file-contents array.  The
resulting merge is written to the file named by I<target_rollrec_file>.
If another I<rollrec> is already open, it is saved and closed prior to
opening the new I<rollrec>.

If I<target_rollrec_file> is an existing I<rollrec> file, its contents will
be merged with the other files passed to I<rollrec_merge()>.  If the file
does not exist, I<rollrec_merge()> will create it and merge the remaining
files into it.

Upon success, I<rollrec_read()> returns the number of I<rollrec>s read from
the file.

Failure return values:

    -1 no rollrec files were given to rollrec_merge
    -2 unable to create target rollrec file
    -3 unable to read first rollrec file
    -4 an error occurred while reading the rollrec names
    -5 rollrec files were duplicated in the list of rollrec files

=item I<rollrec_names()>

This routine returns a list of the I<rollrec> names from the file.

=item I<rollrec_read(rollrec_file)>

This interface reads the specified I<rollrec> file and parses it into a
I<rollrec> hash table and a file-contents array.  I<rollrec_read()> B<must> be
called prior to any of the other B<rollrec.pm> calls.  If another I<rollrec>
is already open, it is saved and closed prior to opening the new I<rollrec>.

I<rollrec_read()> attempts to open the I<rollrec> file for reading and
writing.  If this fails, then it attempts to open the file for reading only.

I<rollrec_read()> is a front-end for I<rollrec_readfile()>.  It sets up the
module's saved data in preparation for reading a new I<rollrec> file.  These
house-keeping actions are not performed by I<rollrec_readfile()>.

Upon success, I<rollrec_read()> returns the number of I<rollrec>s read from
the file.

Failure return values:

    -1 specified rollrec file doesn't exit
    -2 unable to open rollrec file
    -3 duplicate rollrec names in file

=item I<rollrec_readfile(rollrec_file_handle)>

This interface reads the specified file handle to a I<rollrec> file and
parses the file contents into a I<rollrec> hash table and a file-contents
array.  The hash table and file-contents array are B<not> cleared prior
to adding data to them.

Upon success, I<rollrec_read()> returns zero.

Failure return values:

    -1 duplicate rollrec names in file

=item I<rollrec_rectype(rollrec_name,rectype)>

Set the type of the specified I<rollrec> record.  The file is
B<not> written after updating the value, but the internal file-modified flag
is set.  The value is saved in both I<%rollrecs> and in I<@rollreclines>.

I<rollrec_name> is the name of the I<rollrec> that will be modified.
I<rectype> is the new type of the I<rollrec>, which must be either "roll"
or "skip".

Return values:

    0 - failure (invalid record type or rollrec not found)
    1 - success

=item I<rollrec_recval(rollrec_name,rollrec_field)>

This routine returns the value of a specified field in a given I<rollrec>.
I<rollrec_name> is the name of the particular I<rollrec> to consult.
I<rollrec_field> is the field name within that I<rollrec>.

For example, the current I<rollrec> file contains the following I<rollrec>.

    roll	"example.com"
                zonefile        "db.example.com"

The call:

    rollrec_recval("example.com","zonefile")

will return the value "db.example.com".

=item I<rollrec_rename(old_rollrec_name,new_rollrec_name)>

This routine renames the I<rollrec> named by I<old_rollrec_name> to
I<new_rollrec_name>.  The actual effect is to change the name in the I<roll>
or I<skip> line is changed to I<new_rollrec_name>.  The name is changed in
the internal version of the the I<rollrec> file only.  The file itself is
not changed, but must be saved by calling either I<rollrec_write()>,
I<rollrec_save()>, or I<rollrec_saveas()>.

I<old_rollrec_name> must be the name of an existing I<rollrec>.  Conversely,
I<new_rollrec_name> must not name an existing I<rollrec>.

Return values:

     0 - success
    -1 - old_rollrec_name was null or empty
    -2 - new_rollrec_name was null or empty
    -3 - old_rollrec_name is not an existing rollrec
    -4 - new_rollrec_name is already a rollrec
    -5 - internal error that should never happen

=item I<rollrec_settime(rollrec_name,val)>

Set the phase-start timestamp in the I<rollrec> specified by I<rollrec_name>
to the current time.  If the optional I<val> parameter is given and it is
zero, then the phase-start timestamp is set to a null value.

The file is B<not> written after updating the value.

=item I<rollrec_setval(rollrec_name,field,value)>

Set the value of a name/field pair in a specified I<rollrec>.  The file is
B<not> written after updating the value, but the internal file-modified flag
is set.  The value is saved in both I<%rollrecs> and in I<@rollreclines>.

I<rollrec_name> is the name of the I<rollrec> that will be modified.  If the
named I<rollrec> does not exist, it will be created as a "roll"-type
I<rollrec>.
I<field> is the I<rollrec> field which will be modified.
I<value> is the new value for the field.

=item I<rollrec_split(new_rollrec_file,rollrec_names)>

Move a set of I<rollrec> entries from the current I<rollrec> file to a new
file.  The moved I<rollrec> entries are removed both from the current file
and from the internal module data representing that file.

The I<new_rollrec_file> parameter holds the name of the new I<rollrec> file.
If this file doesn't exist, it will be created.  If it does exist, the
I<rollrec> entries will be appended to that file.

I<rollrec_names> is a list of I<rollrec> entries that will be moved from the
current file to the file named in I<new_rollrec_file>.  If some of the given
I<rollrec> names are invalid, the valid names will be moved to the new file
and the invalid names will be returned in a list to the caller.

On success, the count of moved I<rollrec> entries is returned.  Error returns
are given below.

Failure return values:
    -1 - no target rollrec file given in new_rollrec_file
    -2 - no rollrec names given in rollrec_names
    -3 - none of the rollrec names given are existing rollrecs
    -4 - unable to open new_rollrec_file
    -5 - invalid rollrec names were specified in rollrec_names,
         followed by the list of bad names.

=item I<rollrec_unlock()>

I<rollrec_unlock()> unlocks the I<rollrec> synchronization file.

=item I<rollrec_write()>

This interface saves the internal version of the I<rollrec> file (opened with
I<rollrec_read()>).  It does not close the file handle.  As an efficiency
measure, an internal modification flag is checked prior to writing the file.
If the program has not modified the contents of the I<rollrec> file, it is not
rewritten.

I<rollrec_write()> gets an exclusive lock on the I<rollrec> file while writing.

=back

=head1 ROLLREC INTERNAL INTERFACES

The interfaces described in this section are intended for internal use by the
B<rollrec.pm> module.  However, there are situations where external entities
may have need of them.  Use with caution, as misuse may result in damaged or
lost I<rollrec> files.

=over 4

=item I<rollrec_init()>

This routine initializes the internal I<rollrec> data.  Pending changes will
be lost.  An open I<rollrec> file handle will remain open, though the data are
no longer held internally.  A new I<rollrec> file must be read in order to use
the B<rollrec.pm> interfaces again.

=item I<rollrec_newrec(type,name)>

This interface creates a new I<rollrec>.  The I<rollrec_name> field in the
I<rollrec> is set to the values of the I<name> parameter.  The I<type>
parameter must be either "roll" or "skip".

=item I<rollrec_default()>

This routine returns the name of the default I<rollrec> file.

=back

=head1 ROLLREC DEBUGGING INTERFACES

The following interfaces display information about the currently parsed
I<rollrec> file.  They are intended to be used for debugging and testing, but
may be useful at other times.

=over 4

=item I<rollrec_dump_hash()>

This routine prints the I<rollrec> file as it is stored internally in a hash
table.  The I<rollrec>s are printed in alphabetical order, with the fields
alphabetized for each I<rollrec>.  New I<rollrec>s and I<rollrec> fields are
alphabetized along with current I<rollrec>s and fields.  Comments from the
I<rollrec> file are not included with the hash table.

=item I<rollrec_dump_array()>

This routine prints the I<rollrec> file as it is stored internally in an
array.  The I<rollrec>s are printed in the order given in the file, with the
fields ordered in the same manner.  New I<rollrec>s are appended to the end
of the array.  I<rollrec> fields added to existing I<rollrec>s are added at
the beginning of the I<rollrec> entry.  Comments and vertical whitespace are
preserved as given in the I<rollrec> file.

=back

=head1 COPYRIGHT

Copyright 2006-2011 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<lsroll(1)>,
B<rollchk(8)>,
B<rollinit(8)>

B<Net::DNS::SEC::Tools::keyrec(3)>,
B<Net::DNS::SEC::Tools::keyrec(5)>

=cut
