#!/usr/pkg/bin/perl
#
# Copyright 2012-2014 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
#
# buildrealms
#
#	This script generates a whole realms environment.
#

use strict;

use Getopt::Long qw(:config no_ignore_case_always);
use Net::DNS::SEC::Tools::conf;
use Net::DNS::SEC::Tools::defaults;
use Net::DNS::SEC::Tools::realm;
use Net::DNS::SEC::Tools::rollrec;
use Net::DNS::SEC::Tools::keyrec;

use Cwd;
use File::Path;

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

#######################################################################
#
# Constants
#

#
# Command-line options specifically for this program.
#
my %opts = ();					# Filled option array.
my @opts = (
		'actions',		# Display actions taken.
		'clear',		# Clean out the staging area.
		'config=s',		# DNSSEC-Tools config file to copy.
		'directory=s',		# Final directory for realms.
		'nobuild',		# Don't build anything.
		'stagedir=s',		# Directory for staging area.

		'quiet',		# Quiet output.
		'verbose',		# Verbose output.
		'help',			# Give help message.
		'Version',		# Display the version number.
	   );

# 'descend',	# Look at files in curdir hierarchy.	(not yet implemented)
# 'generate',	# Run zonesigner scripts on zones.	(not yet implemented)
 
#
# Variables for command line options.
#
my $actions;			# Display actions taken.
my $clearstage;			# Clear the staging area before starting.
my $conffile;			# DNSSEC-Tools configuration file to copy.
my $directory;			# Install directory that isn't current dir.
my $nobuild;			# Don't build, but show actions.
my $stagedir;			# Directory for staging area.

my $quiet;			# Minimal or no output.
my $verbose;			# Verbose-output flag.

#
# Standard paths.
#
my $CP	  = '/bin/cp';
my $MKDIR = '/bin/mkdir';
my $MV	  = '/bin/mv';

my $STAGEDIR = './staging-buildrealms';

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

my $cwd;					# Current directory.
my $realmfile;					# Realm file to read.
my %realms = ();				# Realm data we'll be needing.
my %hoard = ();					# Realms' file hoards.

my @actions = ();				# Actions we took.

#
# Valid commands.
#
my @cmdlist =
(
	'add',
	'create',
	'find',
	'move',
	'trees',
);

#
# Commands which have yet to be implemented.
#
my %nyis =
(
	'add'		=> 1,
	'find'		=> 1,
	'move'		=> 1,
);

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

main();
exit(0);

#-----------------------------------------------------------------------------
# Routine:	main()
#
# Purpose:	Do everything.
#
sub main
{
	my $command;					# Command to execute.

	#
	# Let's just not run as root.
	#
	if(($< == 0) || ($> == 0))
	{
		print STDERR "do not run $NAME as root\n";
		exit(1);
	}

	#
	# Do a bit of set up.
	#
	$cwd = getcwd();
	$command = optsandargs();

	#
	# Get the required data from the realm file.
	#
	readrealm();

	#
	#
	#
	if($command eq 'trees')
	{
		makestage();	# Create our staging area.
		buildtrees();	# Create the various hierarchies we'll need.
	}
	elsif($command eq 'create')
	{
		makestage();	# Create our staging area.
		buildtrees();	# Create the various hierarchies we'll need.
		movefiles();	# Move files into staging hierarchies.
	}
	else
	{
		print "ain't doing nuffin'\n";
	}

	printactions();
}

#-----------------------------------------------------------------------------
# Routine:	optsandargs()
#
# Purpose:	Parse the command line for options and arguments.
#
sub optsandargs
{
	my $command;				# Command to be executed.
	my $len;				# Length of command name.
	my $ind;				# Command loop index.

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

	usage()   if(defined($opts{'help'}));
	version() if(defined($opts{'Version'}));

	#
	# Get shortcuts for the options.
	#
	$actions    = $opts{'actions'}	 || 0;
	$clearstage = $opts{'clear'}	 || 0;
	$conffile   = $opts{'config'}	 || '';
	$directory  = $opts{'directory'} || $cwd;
	$nobuild    = $opts{'nobuild'}	 || 0;
	$stagedir   = $opts{'stagedir'}	 || $STAGEDIR;

	$quiet	    = $opts{'quiet'}	 || 0;
	$verbose    = $opts{'verbose'}	 || 0;

	#
	# Ensure the config file is an existing, regular, readable file.
	#
	if($conffile ne '')
	{
		if((! -e $conffile) || (! -f $conffile) || (! -r $conffile))
		{
			print STDERR "invalid configuration file specified:  $conffile\n";
			exit(2);
		}
	}
	else
	{
		my $cf = getconffile();
		if((! -e $cf) || (! -f $cf) || (! -r $cf))
		{
			print STDERR "default configuration file invalid:  $cf\n";
			exit(3);
		}
	}

	#
	# Ensure that we're not supposed to be quietly verbose.
	#
	if($quiet && $verbose)
	{
		print STDERR "-quiet and -verbose are mutually exclusive\n";
		exit(4);
	}

	#
	# Ensure we've got some arguments.
	#
	if(@ARGV < 2)
	{
		print STDERR "missing argument\n";
		usage();
	}

	#
	# Get the required arguments.
	#
	$realmfile = $ARGV[0];
	$command   = $ARGV[1];

	#
	# Make sure a command was given.
	#
	if(($len=length($command)) < 1)
	{
		print STDERR "empty command given\n";
		exit(5);
	}

	#
	# Figure out which command was specified and return the full name.
	#
	for($ind=0; $ind < @cmdlist; $ind++)
	{
		my $cmd = $cmdlist[$ind];
		$cmd = substr($cmd, 0, $len);

		if($command eq $cmd)
		{
			$command = $cmdlist[$ind];
			last;
		}
	}

	#
	# Error exit if a bad command was given.
	#
	if($ind == @cmdlist)
	{
		print STDERR "unrecognized command:  $command\n";
		exit(6);
	}

	#
	# Ensure the command has been implemented.
	#
	if(defined($nyis{$command}))
	{
		print STDERR "command $command is not yet implemented\n";
		exit(7);
	}

	return($command);
}

#-----------------------------------------------------------------------------
# Routine:	readrealm()
#
# Purpose:	Read the realm file and squirrel away some data for each realm.
#		The realm file has a record that only we use.  We'll delete
#		that record if everything else goes okay.
#
sub readrealm
{
	my $ret;					# Return value.
	my $errs = 0;					# Error count.

	#
	# Read the realm file.
	#
	realm_lock();
	$ret = realm_read($realmfile);

	#
	# Ensure we can read the realm file.
	#
	if($ret < 0)
	{
		print STDERR "unable to read realm file \"$realmfile\"\n";
		exit(8);
	}

	#
	# Save the realm data we'll actually be needing.
	#
	foreach my $realm (realm_names())
	{
		my $hoard;				# Realm's hoard.
		my $rr = realm_fullrec($realm);		# Get the realm's info.

		$hoard = $rr->{'hoard'};
		$realms{$realm}{'hoard'}     = $hoard;

		$realms{$realm}{'configdir'} = $rr->{'configdir'};
		$realms{$realm}{'realmdir'}  = $rr->{'realmdir'};
		$realms{$realm}{'rollrec'}   = $rr->{'rollrec'};
		$realms{$realm}{'statedir'}  = $rr->{'statedir'};

		#
		# Ensure the realm's file hoard is defined in realm file.
		#
		if(! defined($hoard))
		{
			print STDERR "$realm:  hoard undefined in realm file\n";
			$errs++;
			next;
		}

		#
		# Ensure the realm's file hoard is a real, accessible directory.
		#
		if(! -e $hoard)
		{
			print STDERR "$realm:  hoard does not exist:  $hoard\n";
			$errs++;
		}
		elsif(! -d $hoard)
		{
			print STDERR "$realm:  hoard is not a directory:  $hoard\n";
			$errs++;
		}
		elsif((! -r $hoard) || (! -w $hoard) || (! -x $hoard))
		{
			print STDERR "$realm:  hoard is inaccessible:  $hoard\n";
			$errs++;
		}

	}

	#
	# Don't continue if we had a problem with any realm's hoard.
	#
	if($errs)
	{
		print STDERR "unable to continue due to errors\n";
		exit(9);
	}

	#
	# Delete the hoard records from the realm file.
	#
	foreach my $realm (realm_names())
	{
		realm_delfield($realm,'hoard');
	}

	#
	# Clean up.
	#
	realm_close();
	realm_unlock();
}

#-----------------------------------------------------------------------------
# Routine:	makestage()
#
# Purpose:	Make the staging directory we'll be using.  If -clear was
#		given, we'll zap an existing file/directory of that name.
#
sub makestage
{
	#
	# Clean out the staging directory (if it exists and clear option
	# was given.)
	#
	if($clearstage && (-e $stagedir))
	{
		if(action("rm -fr $stagedir") < 0)
		{
			print STDERR "unable to clear staging directory \"$stagedir\"\n";
			exit(10);
		}
	}

	#
	# Create the directory for the staging area.
	#
	if(action("$MKDIR -p $stagedir") < 0)
	{
		print STDERR "unable to create staging directory \"$stagedir\"\n";
		exit(11);
	}

	print "staging area \"$stagedir\" created\n" if(! $quiet);
}

#-----------------------------------------------------------------------------
# Routine:	buildtrees()
#
# Purpose:	Create the various file trees we *know* we'll need.  These
#		are the three directories given in each realm record, as
#		well as a "dnssec-tools" directory in the config directory.
#
sub buildtrees
{
	my $errs = 0;					# Error count.

	print "building file hierarchies in staging area\n" if(! $quiet);

	#
	# Create the directories for each realm.
	#
	foreach my $realm (sort(keys(%realms)))
	{
		my $newconf;			# Actual config directory.

		#
		# Create this realm's directories in the staging area.
		#
		foreach my $dirtype (qw/configdir realmdir statedir/)
		{
			my $dir = "$stagedir/$realms{$realm}{$dirtype}";

			next if(-e $dir);

			if(action("$MKDIR -p $dir") < 0)
			{
				print STDERR "$realm:  unable to create $dirtype directory \"$dir\" in staging area\n";
				$errs++;
			}
		}

		#
		# Create the actual directory that will hold this realm's
		# config files.
		#
		$newconf= "$stagedir/$realms{$realm}{'configdir'}/dnssec-tools";
		if(action("$MKDIR -p $newconf") < 0)
		{
			print STDERR "$realm:  unable to create config directory \"$newconf\"\n";
			$errs++;
		}

	}

	exit(12) if($errs);
}

#-----------------------------------------------------------------------------
# Routine:	movefiles()
#
# Purpose:	Move and build the various files into the locations in the
#		staging area.  A configuration file is built for the realm,
#		the realm's rollrec file and anything else from the hoard
#		is moved into the staging area.
#
sub movefiles
{
	my $errs = 0;					# Error count.

	#
	# Create the directories for each realm.
	#
	foreach my $realm (sort(keys(%realms)))
	{
		my $dest;				# Destination directory.
		my $rr;					# Rollrec file.
		my $hrr;				# Path to rollrec file.
		my $hoard;				# Realm's file hoard.

		print "moving files for realm $realm\n" if(! $quiet);

		#
		# Get the hoard for this realm.
		#
		$hoard = $realms{$realm}{'hoard'};
		vprint("$realm:  building realm from $hoard");
		filelist($realm,$hoard);

		#
		# Get some Important Values.
		#
		$dest = "$stagedir/$realms{$realm}{'realmdir'}";
		$rr = $realms{$realm}{'rollrec'};
		$hrr = $hoard{$rr};

		#
		# Get a config file.
		#
		$errs += makeconfig($realm,$hoard);

		#
		# Move the bulk of realm's file hoard to its new home.
		#
		$errs += movehoard($realm,$hrr,$dest);

		#
		# Move the rollrec file.
		#
		$errs += movefile($realm,$rr,$dest,'rollrec');

	}

	#
	# Move the realm file.
	#
	if(action("$CP $realmfile $stagedir") < 0)
	{
		print STDERR "unable to copy realm file $realmfile to staging area\n";
		$errs++;
	}

	vprint("\n$errs errors encountered when moving files\n\n");
}

#-----------------------------------------------------------------------------
# Routine:	makeconfig()
#
# Purpose:	Put a DNSSEC-Tools configuration file in a realm's staging
#		area.  The new config file can come from one of three places:
#			- command-line argument (copied to all realms)
#			- a .conf file in realm's hoard (copied to that realm
#			  only)
#			- system's config file (copied to realms without a
#			  .conf)
#
sub makeconfig
{
	my $realm = shift;			# Realm we're working on.
	my $hoard = shift;			# Realm's file hoard.

	my $newconf;				# New config file location.
	my $cf = $conffile;			# Path to source config file.
	my $cffile;				# Path to new config file.
	my $newarch;				# Path to new key archive.
	my $newrlog;				# Path to new rollerd logfile.
	my $cmd;				# Command to execute.
	my $errs = 0;				# Error count.
	my $rc;					# Return code from system().

	#
	# Get the location for the new configuration file.
	#
	$newconf = "$stagedir/$realms{$realm}{'configdir'}/dnssec-tools";

	#
	# Put a new config file in the staging area.  If a config file wasn't
	# specified by the user, then we'll either use the system config file
	# or a .conf file from the realm's hoard.
	#
	if($cf eq '')
	{
		#
		# Look for a <foo>.conf file in the realm's hoard.  If we
		# find one, we'll use it for this realm.
		#
		foreach my $fn (keys(%hoard))
		{
			if($fn =~ /\.conf$/)
			{
				$cf = $hoard{$fn};
				last;
			}
		}

		#
		# If we didn't find a .conf file in the hoard, we'll copy
		# the system's config file into the realm's staging area.
		#
		if($cf eq '')
		{
			$cf = getconffile();
			vprint("$realm:  copying default config $cf to $newconf");
			if(action("$CP $cf $newconf") < 0)
			{
				print STDERR "$realm:  unable to copy default config to $newconf in staging area\n";
				$errs++;
			}
		}
		else
		{
			#
			# Copy the hoard's .conf file to the staging area.
			#
			vprint("$realm:  moving config $cf from hoard to $newconf");
			if(action("$MV $cf $newconf") < 0)
			{
				print STDERR "$realm:  unable to move config $cf to $newconf in staging area\n";
				$errs++;
			}
		}
	}
	else
	{
		#
		# Copy the .conf file given on the command line to the
		# staging area.
		#
		vprint("$realm:  copying config template $cf to $newconf");
		if(action("$CP $cf $newconf") < 0)
		{
			print STDERR "$realm:  unable to copy config $cf to $newconf in staging area\n";
			$errs++;
		}
	}

	#
	# Get the path to the new config file, the new key archive, and the
	# new rollerd log file.
	#
	$cffile  = "$newconf/" . endnode($cf);
	$newarch = "$directory/$realms{$realm}{'statedir'}/key-archive";
	$newrlog = "$directory/$realms{$realm}{'configdir'}/log.rollerd";

	#
	# Escape the path separators.
	#
	$newarch =~ s/\//\\\//g;
	$newrlog =~ s/\//\\\//g;

	#
	# Adjust the archive directory in the config file.
	#
	vprint("$realm:  updating key archive in $cffile");
	$cmd = 'perl -pi -e \'s/^(archivedir\s+).*/$1' . "$newarch/' $cffile";
	$rc = system($cmd);
	if(($rc >> 8) != 0)
	{
		print STDERR "$realm:  unable to update the key archive in $cffile\n";
		$errs++;
	}

	#
	# Adjust the rollerd logfile in the config file.
	#
	vprint("$realm:  updating rollerd logfile in $cffile");
	$cmd = 'perl -pi -e \'s/^(roll_logfile\s+).*/$1' . "$newrlog/' $cffile";
	$rc = system($cmd);
	if(($rc >> 8) != 0)
	{
		print STDERR "$realm:  unable to update the rollerd logfile in $cffile\n";
		$errs++;
	}

	#
	# Return the error count.
	#
	return($errs);
}

#-----------------------------------------------------------------------------
# Routine:	movehoard()
#
# Purpose:	Move the contents of a file hoard into the staging area.
#
sub movehoard
{
	my $realm = shift;			# Realm we're moving.
	my $hrr = shift;			# Rollrec file we're looking at.
	my $dest = shift;			# File destination.

	my $hoard;				# Hoard directory.
	my $errs = 0;				# Error count.

	#
	# Get a shortcut to the hoard.
	#
	$hoard = $realms{$realm}{'hoard'};

	#
	# Read this realm's rollrec.
	#
	rollrec_read($hrr);

	#
	# Get the keyrec and zonefile for each rollrec entry.
	# We'll also dig into the keyrec for keyfiles.
	#
	foreach my $rrname (sort(rollrec_names()))
	{
		my $krf;			# Keyrec file.
		my $szf;			# Signed zone file.

		#
		# Get the fields we need from the rollrec entry.
		#
		$krf = rollrec_recval($rrname,'keyrec');
		$szf = rollrec_recval($rrname,'zonefile');

		$errs += movekeyrec($realm,$krf,$dest);

		$errs += movefile($realm,$krf,"$dest/$krf",'keyrec');
		$errs += movefile($realm,$szf,"$dest/$szf",'signed zonefile');
	}

	#
	# Move anything else in this hoard directory to the staging area.
	#
	foreach my $other (glob("$hoard/*"))
	{
		#
		# Except for the rollrec file.
		#
		next if($other eq $hrr);

		$other =~ s/^$hoard\///;
		$errs += movefile($realm,$other,"$dest/$other",'other');
	}

	#
	# Close the rollrec and return our error count.
	#
	rollrec_close();
	return($errs);
}

#-----------------------------------------------------------------------------
# Routine:	movekeyrec()
#
# Purpose:	Move the contents of a keyrec file into a given location.
#
#		Make some adjustments, depending on record type:
#			- zone records:
#				- move the zone file
#				- adjust the kskdirectory field
#				- adjust the zskdirectory field
#
#			- set records:
#				(nothing yet)
#
#			- key records:
#				- adjust the keypath field
#
sub movekeyrec
{
	my $realm = shift;			# Realm we're moving.
	my $krf = shift;			# Keyrec file we're looking at.
	my $dest = shift;			# File destination.
	my $errs = 0;				# Error count.

	my $fullhoard;				# Abspath to file hoard.
	my $fulldest;				# Abspath to destination.

	#
	# Get some full paths we'll need.
	#
	$fulldest = "$cwd/$dest";
	$fullhoard = "$cwd/$realms{$realm}{'hoard'}";
	$fulldest =~ s/\/\.\//\//g;

	#
	# Read the keyrec.
	#
	keyrec_read($hoard{$krf});

	#
	# Get the keyrec and zonefile from the keyrec file.
	# We'll also dig into the keyrec and adjust some paths.
	#
	foreach my $krname (sort(keyrec_names()))
	{
		my $krtype;					# Keyrec type.

		#
		# Get the keyrec's type.
		#
		$krtype = keyrec_recval($krname,'keyrec_type');

		#
		# Make the adjustments.
		#
		if($krtype eq 'zone')
		{
			my $zone;		# Zone file.
			my $newarch;		# New key archive directory.
			my $oldarch;		# Old key archive directory.
			my $tmparch;		# Temporary archive directory.

			#
			# Move the zonefile.
			#
			$zone = keyrec_recval($krname,'zonefile');
			vprint("$realm:  $krname:  moving zonefile");
			$errs +=movefile($realm,$zone,"$dest/$zone",'zonefile');

			#
			# Adjust the key directory paths in the keyrec.
			#
			vprint("$realm:  $krname:  adjusting key directory paths in keyrec");
			foreach my $dt (qw/kskdirectory zskdirectory/)
			{
				my $keydir;		# Directory from keyrec.

				$keydir = keyrec_recval($krname,$dt);
				$keydir =~ s/^$fullhoard/$directory/;
				keyrec_setval('zone',$krname,$dt,$keydir);
			}

			#
			# Make a new key archive directory,
			#
			if($stagedir =~ /^\//)
			{
				$tmparch = "$stagedir/$realms{$realm}{'statedir'}/key-archive";
			}
			else
			{
				$tmparch = "$cwd/$stagedir/$realms{$realm}{'statedir'}/key-archive";
			}
			$tmparch =~ s/\/\.\//\//g;
			vprint("$realm:  $krname:  making new key archive $tmparch");
			if(! -e $tmparch)
			{
				if(action("$MKDIR $tmparch") < 0)
				{
					print STDERR "$realm:  unable to make new key archive $tmparch in staging area\n";
					$errs++;
					next;
				}
			}

			#
			# Build the name of the new archive directory.
			#
			$newarch = "$directory/$realms{$realm}{'statedir'}/key-archive";
			$newarch =~ s/\/\.\//\//g;

			#
			# Adjust the keypaths for keys in the key archive.
			#
			$oldarch = keyrec_recval($krname,'archivedir');
			vprint("$realm:  $krname:  adjusting keypaths for archived keys");
			foreach my $key (sort(keyrec_names()))
			{
				my $archkey = keyrec_recval($key,'keypath');

				next if(! defined($archkey));
				next if($archkey !~ /^$oldarch\//);

				if(-e $archkey)
				{
					if(action("$MV $archkey $tmparch") < 0)
					{
						print STDERR "$realm:  unable to move old archived key $archkey into new key archive in staging area\n";
						return(1);
					}
				}

				$archkey =~ s/^$oldarch\//$newarch\//;
				keyrec_setval('key',$key,'keypath',$archkey);
			}

			vprint("$realm:  $krname:  adjusting archivedir for zone keyrec");
			keyrec_setval('zone',$krname,'archivedir',$newarch);

		}
		elsif($krtype eq 'set')
		{
		}
		elsif($krtype =~ /[zk]sk/)
		{
			my $keydir;		# Directory from keyrec.

			#
			# If this record has a keypath, we'll point it into
			# the new directory.
			#
			$keydir = keyrec_recval($krname,'keypath');
			if(defined($keydir))
			{
				$keydir =~ s/^$fullhoard/$directory/;
				keyrec_setval('key',$krname,'keypath',$keydir);
			}
		}

	}

	#
	# Close the keyrec and return our error count.
	#
	keyrec_close();
	return($errs);
}

#-----------------------------------------------------------------------------
# Routine:	movefile()
#
# Purpose:	Move a single file in a realm's file hoard to a given location.
#		We won't move the rollrec file, as that's done elsewhere.
#		It isn't just a straight move, as there's some validation and
#		path finagling that goes along for the ride.
#
sub movefile
{
	my $realm = shift;				# Realm we're building.
	my $fn = shift;					# File to move.
	my $dest = shift;				# Destination directory.
	my $desc = shift;				# Description of file.

	my $hfn;					# Hoard path to file.
	my $destpath;					# Hoard path to file.

	#
	# Ensure we've found the source file.
	#
	if(! defined($fn))
	{
		print STDERR "$realm:  $desc not defined\n";
		return(1);
	}

	#
	# Ensure we've found the source file.
	#
	$hfn = $hoard{$fn};
	if(!defined($hfn))
	{
		print STDERR "$realm:  unable to find $desc \"$hfn\"\n";
		return(1);
	}

	#
	# Get rid of the hoard prefix from the destination path.
	#
	$destpath = $hfn;
	$destpath =~ s/^$realms{$realm}{'hoard'}\/*//;

	#
	# If the source file path contains any directories, we'll create
	# them in the destination.  But not for the rollrec file.
	#
	if(($destpath =~ /\//) && ($desc ne 'rollrec'))
	{
		my @nodes;				# Nodes in path.
		my $path;				# Path without file.

		@nodes = split /\//, $destpath;
		pop @nodes;
		$path = join('/', @nodes);

		if(action("$MKDIR -p $dest/$path") < 0)
		{
			print STDERR "$realm:  unable to create directory \"$dest\" in staging area\n";
			return(1);
		}
	}

	#
	# Move the file.
	#
	if(action("$MV $hfn $dest") < 0)
	{
		print STDERR "$realm:  unable to move $desc $hfn to $dest in staging area\n";
		return(1);
	}

	return(0);
}

#-----------------------------------------------------------------------------
# Routine:	filelist()
#
# Purpose:	Build a hash of files in a given file hoard.  The hash key
#		is the node name, the value is the path.
#
sub filelist
{
	my $realm = shift;			# Realm that owns hoard.
	my $dir = shift;			# Hoard directory to index.
	my $ret;				# Return value.
	my @files;				# Files in this hoard.
	my $errs = 0;				# Error count.

	#
	# Ensure we were given a directory.
	#
	return if($dir eq '');

	#
	# Reset the hoard.
	#
	%hoard = ();

	#
	# List all the files in this hoard.
	#
	@files = `find $dir -print`;

	#
	# Build a hash of all our files.
	#
	foreach my $path (sort(@files))
	{
		my $fn;					# Path's node.

		#
		# Skip the current directory, the hoard directory, and
		# anything in the staging area.
		#
		chomp $path;
		next if(($path eq '.')			  ||
			($path eq "$dir")		  ||
			($path =~ /^\.\/$stagedir\//));

		#
		# Get the last element of the path.
		#
		$fn = endnode($path);

		#
		# If we've already seen a node by this name, we'll complain.
		# If we haven't seen it, we'll add it to our list.
		#
		if(defined($hoard{$fn}))
		{
			print STDERR "$realm:  $hoard{fn} is a duplicated file names\n";
			$errs++;
		}
		else
		{
			$hoard{$fn} = $path;
		}
	}

	vprint("duplicated file names:  " . $errs);
}

#-----------------------------------------------------------------------------
# Routine:	action()
#
# Purpose:	Run an external command.  The command will be saved in
#		a list of actions and the command's return code returned.
#
sub action
{
	my $act = shift;				# Action to take.
	my $ret;					# Action's return code.

	system($act) if(! $nobuild);
	$ret = $?;

	push @actions, $act;

	return($ret);
}

#-----------------------------------------------------------------------------
# Routine:	printactions()
#
# Purpose:	Return the final node in a path.
#
sub endnode
{
	my $path = shift;				# Path to parse.
	my @chunks;					# Pieces of path.

	@chunks = split '/', $path;
	return($chunks[-1]);
}

#-----------------------------------------------------------------------------
# Routine:	printactions()
#
# Purpose:	Print a list of the actions taken if the user wants it.
#
sub printactions
{
	return if((! $actions) || $quiet);

	print "\nactions:\n";
	foreach my $act (@actions)
	{
		print "$act\n";
	}
}

#-----------------------------------------------------------------------------
# Routine:	vprint()
#
sub vprint
{
	my $str = shift;				# String to print.

	print "$str\n" if($verbose);
}

#-----------------------------------------------------------------------------
# Routine:	usage()
#
sub usage
{
	print "usage: $0 [options] <realmsfile> <command> <arguments>\n";
	print "\twhere <command> may be:\n";
	print "\t\tactions      print actions taken\n";
	print "\t\tcreate       create a new realms hierarchy\n";
	print "\t\tadd          add realms to existing realms hierarchy\n";
	print "\t\tfind         find files in given directories\n";
	print "\t\ttree         create hierarchy, but don't move files\n";
	print "\n";
	print "\twhere <options> may be:\n";
	print "\t\t-actions     display actions taken\n";
	print "\t\t-clear       clean out the staging area\n";
	print "\t\t-config      DNSSEC-Tools config file to copy\n";
	print "\t\t-directory   final directory for realms\n";
	print "\t\t-nobuild     don't build anything\n";
	print "\t\t-stagedir    directory for staging area\n";
	print "\n";
	print "\t\t-quiet       quiet output\n";
	print "\t\t-verbose     verbose output\n";
	print "\t\t-help        give help message\n";
	print "\t\t-Version     display the version number\n";

	exit(0);
}

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

	exit(0);
}

1;

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

=pod

=head1 NAME

buildrealms - assist in building a DNSSEC-Tools realms environment

=head1 SYNOPSIS

  buildrealms [options] <realmsfile> <command> <command-args>

=head1 DESCRIPTION

B<buildrealms> helps in setting up a realms environment for use by B<dtrealms>.
B<buildrealms> creates the required file hierarchies for each realm, it moves
a realm's files to their appropriate place in the hierarchy, and it updates
several files for the final destination.

The realm hierarchies are built in a staging area, which will hold the files
for all the realms.  These are I<rollrec> files, I<keyrec> files, key files,
configuration files, log files, and anything else needed for by DNSSEC-Tools
to manage key rollover.  After B<buildrealms> creates these files, the user
should check the files to ensure that they are correct.  The files and
directories in the staging then must be manually moved to the final directory.
It is from this directory that B<dtrealms> will manage the various realms.
If the final directory isn't specified (via an option), then the directory
in which B<buildrealms> was executed will be the final directory.

B<buildrealms> uses a I<realms> file to control how it builds the realms
environment.  This B<realm> entries in this file have a I<hoard> field,
which is only used by B<buildrealms>.  For each realm, this field's value
is a directory which holds the files needed by that particular realm.
After building that realm, B<buildrealms> removes the I<hoard> entry from
that B<realm> record.  After all the realms have been built, a copy of this
I<realms> file is moved into the staging area.

There are two operations B<buildrealms> currently provides.  These operations
are in support of creating and maintaining a DNSSEC-Tools realms environment.
This documentation describes the operations individually.

=head2 Realms Environment Creation

The I<create> command builds the whole realms environment.  The realm file
hierarchies are built in the staging area.  After B<buildrealms> creates these
files, the user should check the files to ensure that they are correct.  The
files and directories in the staging then must be manually moved to the final
directory.  If the final directory isn't specified (via an option), then the
directory in which B<buildrealms> was executed will be the final directory.

B<buildrealms> takes the following actions when given the I<create> command:

=over 4

=item * A file hierarchy is created for each realm.

=item * A DNSSEC-Tools configuration file is put in each realm's hierarchy.
If the I<-config> option is given, then the specified configuration file
will be copied to each realm.  If it isn't given, then each realm's hoard
will be searched for a file whose name ends with B<.conf>.  The first such
file found will be used for that realm only.  If such a file is not found,
then the system-wide DNSSEC-Tools configuration file will be used for that
realm.

=item * The realm's I<rollrec>, I<keyrec>, zone, and key files are moved into
the hierarchy.  The I<rollrec> file is named in the I<realms> file.  The
I<keyrec> and signed zone files are listed in the I<rollrec> file.  The
unsigned zone files and key files are listed in the I<keyrec> file.

=item * A key archive is created for each realm's existing, expired keys.
The key archive is placed in the realm's state directory in the staging
area.  Archived keys, as listed in the I<keyrec> files, are moved to this
key archive.

=item * Paths in several files are adjusted for the new hierarchy and the
realm's final destination.  These paths include archived keys in the realm's
I<keyrec> files, the key archive and B<rollerd> log files in the realm's
DNSSEC-Tools configuration file, and key directories in the I<keyrec> files.

=back

=head2 Realms Hierarchy Creation

The I<trees> command builds the basic directory hierarchy for each realm in
the staging area.  However, no other files or directories are copied or moved
in to the staging area..

The following directories are created for each realm:

=over 4

=item * configuration directory - This holds the B<dnssec-tools> directory.

=item * dnssec-tools directory - This will hold the DNSSEC-Tools configuration
file.

=item * state directory - This will hold the realm's state information,
including the key archive.

=item * realm directory - This will hold  the realm's I<rollrec> file, the
I<keyrec> files, the zone files (signed and unsigned), and the key files.

=back

=head1 PREPARING FOR EXECUTION

In preparing a I<realms> file and the realm hoards for B<buildrealms>, there
are several things that should be kept in mind.

=over 4

=item * Use relative paths for the I<rollrec> file and three directories in
the I<realms> file.

=item * All a realm's files should be stored in its hoard.  They do not have
to be in a particular place in the directory, as long as the I<rollrec> and
I<keyrec> files are accurate.

=item * At the end of the creation process, the I<realms> file will be
copied into the top level of the staging area.

=item * After specific files (e.g., I<rollrec>s, I<keyrec>s, etc.) are moved
into a realm's part of the staging area, the remaining files in the hoard
will be moved into the realm's I<realmdir> part of the staging area.  The
hierarchical organization of the remaining hoard files will be preserved.

=item * The contents of a I<keyrec>'s archive directory in the realm's
hoard, as defined by the I<archivedir> field, will be moved to
B<E<lt>statedirE<gt>/key-archive> in the staging area.

=item * The configuration file for a realm will be put in
B<E<lt>configdirE<gt>/dnssec-tools/E<lt>conffileE<gt>> in the staging area.
The actual name of the configuration file (given here as B<E<lt>conffileE<gt>>)
will depend on how the configuration file is found.  If the system-wide
DNSSEC-Tools file is used, then the name will be B<dnssec-tools.conf>.  If
the I<-config> option is used, then the name used with the option will be
used.  If a B<.conf> file is found in the realm's hoard, then the full
filename will be used.

=back

=head1 WARNINGS

I<root> is not allowed to run B<buildrealms>.  Some of the actions taken
by B<buildrealms> can be devastating if a misconfigured (or maliciously
constructed) I<realm> file is used to control construction.

B<buildrealms> is not clairvoyant.  It does the best it can, but it is a
general tool.  The resulting realms should be checked to ensure they are set
up as desired.  In particular, you should check the B<realm> file I<rollrec>
files, I<keyrec> files, and configuration file.

No reverse functionality has been implemented, so once run, the files are
modified, moved, and copied.  It might not be a bad idea to back up your files
I<prior> to running B<buildrealms>, just in case...

=head1 COMMANDS

=over 4

=item B<create>

The B<create> command builds the whole realms environment.  B<buildrealms>
takes the following actions when given this command:

=item B<trees>

The B<trees> command builds the basic directory hierarchy for each realm.
The following directories are created for each realm:

=back

=head1 OPTIONS

=over 4

=item B<-actions>

Display the file actions taken by B<buildrealms>.  This includes directory
creations, file copies, and file moves.  If used in conjunction with the
B<-nobuild> option, B<buildrealms> will not perform the actions, but will
display the actions that would otherwise have been taken. 

=item B<-clear>

This flag indicates that B<buildrealms> should delete the current staging
area and its contents prior to building the realms.

=item B<-config conffile>

B<conffile> is the DNSSEC-Tools configuration file to copy for each realm.

=item B<-directory target>

B<target> is the target directory for the realms to be built by
B<buildrealms>.  The new realms will not be moved to this directory,
but the realms' files will reflect the use of this directory.  If this
option is not specified, the current directory will be used.

If B<-directory> and B<-stagedir> use the same directory, then the
realms environment will be build in the final directory.

=item B<-nobuild>

This option tells B<buildrealms> to go through the motions of building the
new realms, but not to actually build anything.  If this is used in
conjunctions with the B<-actions> option, B<buildrealms> will show the
actions that would have been taken.

=item B<-stagedir directory>

This directory in which the new realms hierarchy is built.  The default
staging area is B<./staging-buildrealms> if this option is not specified.

If B<-directory> and B<-stagedir> use the same directory, then the realms
environment will be build in the final directory.

=item B<-quiet>

B<buildrealms> is prevented from printing any non-error output.
This option and the B<-verbose> option are mutually exclusive.

=item B<-verbose>

B<buildrealms> prints a lot of information about what it is doing.
This option and the B<-quiet> option are mutually exclusive.

=item B<-Version>

Displays the version number.

=item B<-help>

Displays a help message.

=back

=head1 EXAMPLES

The following examples may help clarify the use of B<buildrealms>.  In each
example, the following I<realms> file will be used.

    realm "example"
        state           "active"
        configdir       "configs/example"
        statedir        "states/example"
        realmdir        "r-example"
        rollrec         "demo-example.rollrec"
        administrator   "zonefolks@example.com"
        display         "1"
        manager         "rollerd"
        args            "-loglevel phase -logfile log.example"
        hoard           "r-example"

    realm "test"
        state           "active"
        realmdir        "r-test"
        configdir       "configs/test"
        statedir        "states/test"
        rollrec         "demo-test.rollrec"
        manager         "rollerd"
        args            "-loglevel tmi -logfile log.test"
        display         "1"
        hoard           "r-test"

=head2 CREATE EXAMPLE

Each realm record contains a I<hoard> field that B<buildrealms> will use to
find that realm's files.  After running B<buildrealms demo.realm create> with
the I<realms> file above, the following directories will be created:

    staging-buildrealms/
    staging-buildrealms/configs/
    staging-buildrealms/configs/example/
    staging-buildrealms/configs/example/dnssec-tools/
    staging-buildrealms/configs/test/
    staging-buildrealms/configs/test/dnssec-tools/

    staging-buildrealms/r-example/
    staging-buildrealms/r-example/dnssec-tools/
    staging-buildrealms/r-test/
    staging-buildrealms/r-test/dnssec-tools/

    staging-buildrealms/states/
    staging-buildrealms/states/example/
    staging-buildrealms/states/example/key-archive/
    staging-buildrealms/states/test/
    staging-buildrealms/states/test/key-archive/

The following files will be moved into the staging area.  In the interests
of brevity this is only a subset of files moved to the staging area; most of
the key files have not been included:

    staging-buildrealms/demo.realm

    staging-buildrealms/configs/example/dnssec-tools/dnssec-tools.conf
    staging-buildrealms/configs/test/dnssec-tools/dnssec-tools.conf

    staging-buildrealms/r-example/demo-example.rollrec
    staging-buildrealms/r-example/demo.com
    staging-buildrealms/r-example/demo.com.signed
    staging-buildrealms/r-example/dsset-demo.com.
    staging-buildrealms/r-example/dsset-example.com.
    staging-buildrealms/r-example/dsset-test.com.
    staging-buildrealms/r-example/example.com
    staging-buildrealms/r-example/example.com.signed
    staging-buildrealms/r-example/Kdemo.com.+005+16933.key
    staging-buildrealms/r-example/Kdemo.com.+005+16933.private
    staging-buildrealms/r-example/test.com
    staging-buildrealms/r-example/test.com.signed

    staging-buildrealms/r-test/demo-test.rollrec
    staging-buildrealms/r-test/dev.com
    staging-buildrealms/r-test/dev.com.signed
    staging-buildrealms/r-test/dsset-dev.com.
    staging-buildrealms/r-test/dsset-test.com.
    staging-buildrealms/r-test/Ktest.com.+005+34236.key
    staging-buildrealms/r-test/Ktest.com.+005+34236.private
    staging-buildrealms/r-test/test.com
    staging-buildrealms/r-test/test.com.signed

=head2 TREES EXAMPLE

After running B<buildrealms demo.realm trees> with the I<realms> file above,
the following directories will be created:

    staging-buildrealms/
    staging-buildrealms/configs/
    staging-buildrealms/configs/example/
    staging-buildrealms/configs/example/dnssec-tools/
    staging-buildrealms/configs/test/
    staging-buildrealms/configs/test/dnssec-tools/

    staging-buildrealms/r-example/
    staging-buildrealms/r-test/

    staging-buildrealms/states/
    staging-buildrealms/states/example/
    staging-buildrealms/states/test/

No additional files or directories are created by this command.

=head1 COPYRIGHT

Copyright 2012-2014 SPARTA, Inc.  All rights reserved.

=head1 AUTHOR

Wayne Morrison, tewok@tislabs.com

=head1 SEE ALSO

B<dtrealms(8)>,
B<realminit(8)>,
B<realmset(8)>

B<keyrec(5)>,
B<realm(5)>,
B<rollrec(5)>

=cut

