#!/usr/pkg/bin/perl


# $Id: maintkeydb,v 0.29 2005/05/24 14:35:47 olaf Exp $


#############################################################################
#  CLI and shell interface to Net::DNS::SEC::MAINT::Key dnssec keys database
#
#  See perldoc maintkeydb for description for end-user.
#
#  (c) 2004 RIPE NCC, Olaf Kolkman
#  licence terms can be found below or by piping this file through perldoc.
#############################################################################




##############################################################
#
# Rough Architecture description.
# The MAINTKEYDB class consitst out of a number of MAINTKEYDB::COMMAND 
# subclasses.
#
# Each MAINTKEYDB::COMMAND  subclass instance has a list of ordered.
# MAINTKEYDB::ARGUMENT objects as attributes.

#
# Each MAINTKEYDB::COMMAND subclass must provide an execute_command method.
# 


# MAINTKEYDB::ARGUMENT::<NAME> classes contain the information on the
# different type of argumets.

# An MAINTKEYDB::ARGUMENT::<NAME> object has a number of attribute
# that store information on how these should be matched against the
# input.
#
# During the initialization of an MAINTKEYDB::ARGUMENT instance the following
# attributes are set.

# The description attribute. Describes which arguments are expected at
# that position
#  e.g.   Description => '<"zsk"|"ksk">',
#
# The Regexp attribute, how to determine a match
#   e.g  Regexp => '(?i)(zsk|ksk)',
#
# The Attribute attribute. Contains the attribute name in a
# MAINTKEYDB::COMMAND instance which should be used to store the
# result of a match in.  Attribute=> "_type",

# The Prohibitive attribute contains a anonymous hash of regular
# expressions that match against the arguments provided and
# explanations.why the combination of that set of arguments is
# prohibited.
#

# The regular expressions are matched agains the whole command line
# to deterimine.
#
#
# In noninteractive mode the match_full_cl method will collect all
# the instances of the MAINTKEYDB::ARGUMENT and do the regular expression
# matching.
#
#
# First all packages(Classes and sub Classes) are defined. At the end
# of this code you can find the code that is actually
# executed. (Search for 'main()' to get to that piece of the code.)
#
###############################################################

package MAINTKEYDB;   # Give the beast its own namespace.

use strict;           # perl -wT and strict 
use Carp qw(confess);             # We have to confess sometimes
use Getopt::Std;      # For parsing command line switches.
use IO::Handle;
use Log::Log4perl qw(get_logger :levels);
    


use vars qw($REVISION @EXPORT @ISA $AUTOLOAD $_KEY_MODULE_LOADED
$_CONFIG_MODULE_LOADED);


BEGIN {
    eval { require Net::DNS::SEC::Maint::Key ; 
	 };
    # $@ will be true if any errors where encountered
    # loading Key.pm
    $_KEY_MODULE_LOADED = $@ ? 0 : 1;


    eval { require Net::DNS::SEC::Maint::Config ; };
    # $@ will be true if any errors where encountered
    # loading Config.pm
    $_CONFIG_MODULE_LOADED = $@ ? 0 : 1;


}


%MAINTKEYDB::Errors = (
    PRINTED_HELP 			=> [1,"INFO"],		      
    NOT_IMPLEMENTED 			=> [2,"INFO"],
    PARSING_FAILED 			=> [3,"INFO"],
    PARSING_FAILED_EXTRA_ARGUMENTS 	=> [4,"INFO"],
    FAILED_TO_CREATE_KEY 		=> [5,"FATAL"],
    ROLLOVER_PREREQUISITS_NOT_MET	=> [6,"INFO"],
    INCONSISTENT_ROLLOVER_SETTING	=> [7,"FATAL"], 
    INIT_ASKARG_FAILED			=> [8,"INFO"], 
    ROLLOVER_OF_KSK_FAILED 		=> [9,"INFO"], 
    ROLLOVER_OF_ZSK_FAILED 		=> [10,"INFO"],
    PROHIBITIVE_ARGUMENT_COMBINATION 	=> [11,"INFO"],
    NO_KEYS_DELETED 			=> [12,"INFO"],
);






$REVISION = do { my @r = ( q$Revision: 0.29 $ =~ /\d+/g ); sprintf
      "%d." . "%03d" x $#r, @r;
};


#####
## log4 perl default configuration
##
##
my $default_log4perl_conf=q(
    log4perl.rootLogger=FATAL,Screen
    log4perl.appender.Logfile          = Log::Log4perl::Appender::File
    log4perl.appender.Logfile.filename = test.log
    log4perl.appender.Logfile.layout   = Log::Log4perl::Layout::PatternLayout
    log4perl.appender.Logfile.layout.ConversionPattern = [%r] %F %L %m%n

    log4perl.appender.Screen         = Log::Log4perl::Appender::Screen
    log4perl.appender.Screen.stderr  = 0
    log4perl.appender.Screen.layout = Log::Log4perl::Layout::SimpleLayout
  );




#
# Need some wrapper to get to the "standard"
#
    if (-f "/usr/local/etc/log4perl.conf"){
	Log::Log4perl::init_once("/usr/local/etc/log4perl.conf");
      }elsif(-f "/etc/log4perl.conf" ){
	  Log::Log4perl::init_once("/etc/log4perl.conf");
	}else{
	    Log::Log4perl::init_once(\$default_log4perl_conf);
	  }





######################################
#
# Make sure things are under our own control shellwise

$ENV{PATH} = '';
delete @ENV{qw {ENV CDPATH ENV BASH_ENV}};





##############################################################
#
# Everything has been set up. now check if we have loaded everything
# 

if ( !  $_KEY_MODULE_LOADED || !  $_CONFIG_MODULE_LOADED ) {
    print STDERR "maintkeydb reversion: " . $REVISION ."\n";
    print STDERR "Net::DNS::SEC::Maint::Key could not be loaded\n" 
      if (!  $_KEY_MODULE_LOADED);
    print STDERR "Net::DNS::SEC::Maint::Config could not be loaded\n" 
      if (!  $_CONFIG_MODULE_LOADED);
#    exit  ERROR_FAILED_LOADING_MODULES;
}

@MAINTKEYDB::ISA = qw( Net::DNS::SEC::Maint::Config);





# method MAINTKEYDB::new
sub new {
    ######
    # Rudimentary method to get "a" username....
    # This needs improvement.
    
    my $userID = "UnKnown";

    if ( exists $ENV{SSHUSER} ) {
        $userID = $ENV{SSHUSER};
    }
    elsif ( exists $ENV{USER} ) {
        $userID = $ENV{USER};
    }

    my $self = bless {_interactive => 0,    # If InterActive==1 we the command is in "shell" mode.

		      _userid => $userID,
		     }, shift;
    die "Failure in creating MAINTKEYDB instance " unless $self;

    my $keydb = Net::DNS::SEC::Maint::Key->new( $userID );

    $self->{_keydb}=$keydb;
    my $fh=IO::Handle->new();
    $fh->fdopen(fileno(STDOUT),"w")|| die "Could not opens STDERR";
    $self->fh($fh);   # Default FH is STDERR.
    return $self;
}

# method MAINTKEYDB::fh
# get or set the filehandle used for printing messages.

sub fh {
    my $self=shift;
    my $arg=shift;
    if (defined $arg) {
	die "Argument is not an IO::Handle" unless $arg->isa("IO::Handle");
	$self->{_fh}->close if defined 	$self->{_fh};
	$arg->autoflush;
	$self->{_fh}=$arg;
	
    }
    return $self->{_fh};
}

# method MAINTKEYDB::verbose
# Set and get the class variable
sub get_verbose {
    my $self=shift;
    return $MAINTKEYDB::Verbose || 0;
}

sub set_verbose {
    my $self=shift;
    my $arg=shift;
    return $MAINTKEYDB::Verbose=$arg;
}


# method MAINTKEYDB::get_term
# Returns the terminal or 0;
sub get_term {
    my $self=shift;
    return $self->{_term}?$self->{_term}:0;
}


# method MAINTKEYDB::exit_error
# purpose provide a unified way to exit on error
#
#
# The method uses %MAINTKEYDB::Errors which is a hash of errorcodes.
# The keys to this hash are to be used as arguments to exit_error.
#
# The values of this hash contain anonymous arrays.
# the first value in the array is the exit code of the application
# the second value is either INFO or FATAL. this value is relevant if the
# maintkeydb is used in interactive mode. a FATAL error will "crash" the shell
# an INFO error will let the user continued.
# The 3rd value of the array is a somewhat more verbose description of
# the error.
#

sub exit_error {
    my $self=shift;
    my $arg=shift;
    my $errorlevel=$MAINTKEYDB::Errors{ uc $arg }[1] ||"FATAL";
    my $logger=get_logger();
    if ($errorlevel eq "INFO" && $self->get_term){
	$logger->info("Exit on error: $arg");
	return $MAINTKEYDB::Errors{ uc $arg }[0] || 255;;
    }else{
	$logger->warn("Exit on error: $arg");
	$self->fh()->print ("Exit on error: $arg\n") if $self->get_verbose;
	exit $MAINTKEYDB::Errors{ uc $arg }[0] || 255;
    }
}
# method MAINTKEYDB::command
# takes a string with a maintkeydb command with arguments, returns a
# MAINTKEYDB::COMMAND type object
#
sub command {
    my $self=shift;
    my $inline=shift;
    my $logger=get_logger();
    $inline=~s/^\s*(\w+)(\b|\s+)(.*)/$3/;
    my $command=uc $1;
    $logger->debug("COMMAND: $command\nArguments: $inline\n");
    print "COMMAND: $command\nArguments: $inline\n" if $self->get_verbose>3;

    # Look in the symbol table if the command has an associated package and
    # if so created an instance blessed into that backage.
    if (defined ($MAINTKEYDB::COMMAND::{$command."::"})){

	# Not all attributes in the MAINTKEYDB are relevant to
	# MAINTKEYDB::COMMAND. Since I experienced problems during
	# debuging with Data::Dumper and an instance of Term::Readline
	# i choose to delete that.

	my %argument=%{$self};

	my $commandinstance="MAINTKEYDB::COMMAND::$command"->_new(%argument);
	$commandinstance->set_cl($inline);
	$commandinstance->set_commandname(lc $command);
	return $commandinstance;
    }else{

	$logger->info("Command \'".lc $command."\' is not implemented\n" );
	$self->fh->print ( "Command \'".lc $command."\' is not implemented\n" );
	return 0;
    }

    confess "Fell through loop contact developer";
}

#
#########################################################################
#
#  MAINTKEYDB::COMMAND class and sublcasses.
#
#########################################################################

package MAINTKEYDB::COMMAND;
use Carp;
use Log::Log4perl qw(get_logger :levels);
use vars qw($AUTOLOAD @ISA);
@MAINTKEYDB::COMMAND::ISA = qw(MAINTKEYDB);



# method MAINTKEYDB::COMMAND::_new
#
# This is a "hidden" constructor. For constructing new COMMAND instances
# one should use the MAINTKEYDB::command method. This way you can be sure
# all attributes from the MAINTKEYDB instance are inherited.
#

sub _new {
    my ($class,%args)=@_;
    my $self=bless {}, ref($class)||$class;   # How simple...
    $self->_init(%args);
    return $self;
}



# method MAINTKEYDB::COMMAND::_init
sub _init {
    my ($self,%args)=@_;
    # This desription is used by MAINTKEYDB::COMMAND::print_help
    # And is therefore pretty generic.
    # Overwritten in the ::COMMAND::FOO::_init functions
    $self->set_helptext("This command can take the following form:\n");
    foreach my $key (keys %args){
	$self->{$key}=$args{$key};
    }
    return $self;
}



# method MAINTKEYDB::COMMAND::get_command_name 
sub get_command_name {
    my $self=shift;
    ref($self) =~ /MAINTKEYDB::COMMAND::(\w*)$/;
    return $1 ? lc $1 : "unknown";
}

# method MAINTKEYDB::COMMAND::set_cl
# purpose get or set the command line with all its arguments.
#
sub set_cl {
    my $self=shift;
    my $arg=shift;
    # Because of parsing the commandline should always have trailing space.
    $self->{_cl}=$arg . " " if defined $arg;	
}


# method MAINTKEYDB::COMMAND::execute_command
# This is a catch-all method. the execute_command method should be implemented
# for each subclass
sub execute_command {
    my $self=shift;
    # This should not happen
    die "execute method for ".ref($self). " has not been implemented yet\n Contact developer";
}







# method MAINTKEYDB::COMMAND::AUTOLOAD
# generic set get method for attributes
sub AUTOLOAD {
    my $self=shift;
    my $arg=shift;
    $AUTOLOAD =~/.*::(get|set)_(\w+)/
      or croak "No such method: $AUTOLOAD\;";
    my $attribute="_".$2;
    if ($1 eq "set"){
	$self->{$attribute}=$arg if defined $arg;	
    }
    return $self->{$attribute} ;
}


# method MAINTKEYDB::COMMAND::get_args_from_user;
# purpose in interactive/shell mode ask the user for all the commands not yet
# specified

sub get_args_from_user {
    my $self=shift;
    my @arguments=$self->get_arguments?@{$self->get_arguments()}:();
    my $historyline=$self->get_commandname();
    die "NO terminal." unless $self->get_term;
    for (my $i=0;$i<@arguments;$i++){
	# use the askarg method from the MAINTKEYDB::ARGUMENT class to
	# proceed with the user interaction.
	my $err=$arguments[$i]->askarg($self) if  
	  (! defined ($self->{$arguments[$i]->get_attributename}) ||
	   ($self->{$arguments[$i]->get_attributename}	eq "___empty___"));
	return $err if $err;
	# Now the argument must be set so lets add the content to the
	# history
	$historyline .= " ". $self->{$arguments[$i]->get_attributename}   unless ($self->{$arguments[$i]->get_attributename}	eq "___empty___");
	
    }
    $self->{_term}->add_history($historyline);
    return 0;
}

# method MAINTKEYDB::COMMAND::match_full_cl 
# purpose: creates a regular expression to match 
# the command line against
# On failure the method calls exit_error.
# On success returns 0 and sets the attributes in the command object



sub match_full_cl {
    my $self=shift;
    my $commandline=$self->get_cl();
    my @arguments=$self->get_arguments?@{$self->get_arguments()}:();

    if ($commandline =~ s/^\s*help\b//){
	# print_help exits in non_interactive shell.
	# it will return when in interactive shell.
	# 
	$self->print_help;
    }

    if ($self->get_prohibitive){
	foreach my $regex (keys %{$self->get_prohibitive}){
	    my $error=$self->get_prohibitive()->{$regex};
	    if ($commandline =~ /$regex/xi ){
		$self->fh->printf ($error."\n",$1,$2);
		return $self->exit_error("prohibitive_argument_combination");
	    }
	}
    }
	
    for (my $i=0;$i<@arguments;$i++){

	my $regexp='^\s*'.$arguments[$i]->get_regexp();
	print "REGEXP: --$commandline--".$regexp ."\n" if $self->get_verbose >5;
	# Note that a command line will always need a trailing space for
	# the folloving regexp to work
	# the MAINTKEYDB::COMMAND::set_cl method takes care of that.
	if ( $commandline=~ s/$regexp//x ){
	    $self->{$arguments[$i]->get_attributename()}=lc $1 || "___empty___";
	}else{
	    if (! $self->get_term ){
		$self->fh->print ("Failed to parse the command line:\n".
				  "maintkeydb ". $self->get_command_name ."  " .$self->get_cl(). " \n");
		if ($commandline =~ /^\s*$/ ){
		    $self->fh->print ( "arguments missing;\n");
		}else {
		    $self->fh->print( "\tthe bit that failed to parse is:\n\t". $commandline ."\n") if $commandline;
		}
		$self->fh->print ( "I expected:". $arguments[$i]->get_description() ."\n");
	    }
	    return $self->exit_error("parsing_failed");
	}
	
    }

    if ($commandline =~ /\S/ ){
	$self->fh->print ("Failed to parse the command line around: ". $commandline ."\n");
	$self->fh->print ( "(Caussed by extra arguments)");
	
	return $self->exit_error("parsing_failed_extra_arguments");
    }
    
    return 0;
    
}


# method MAINTKEYDB::COMMAND::print_help
# prints the expecte arguments
#
sub print_help {
    
    my $self=shift;
    ref($self)=~/MAINTKEYDB::COMMAND::(.*)$/;

    my $command=$1;
    my @arguments=$self->get_arguments?@{$self->get_arguments()}:();
    $self->fh->print ($self->get_helptext);
    $self->fh->print (lc $command ." ");
    foreach my $argument (@arguments){
	$self->fh->print ($argument->get_description ." ");

    }
    print "\n";
    return $self->exit_error("printed_help");

}


######################################################################
#
# maintkeydb create ["force"]  <"ksk"|"zsk"|"both"> <RSASHA1|DSA|RSA|RSAMD5> <size> \
#	<zone[[ zone],...]>

package MAINTKEYDB::COMMAND::CREATE;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::COMMAND::CREATE::ISA = qw(MAINTKEYDB::COMMAND);

sub _init {
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);
    $self->set_helptext("Purpose: create new keys\n".

"You can choose to generate a ksk, a zsk or both keys. 

If keys are first created for a zone then multiple zone signing keys
are created, thereby bootstrapping the prerequisits for automatic
rollover using the rollover command. The system will not create keys
if it thereby might disable automatic rollover. Use \"force\" to force
creation of keys.  
The usage is as follows:\n" );


    $self->set_prohibitive(
			   {'((?i)(ksk|zsk).*force)' => 
			    "ksk and force argument in wrong order",
			    '(\d+).*(RSASHA1|DSA|RSA|RSAMD5)' =>
			    "size should be specified behind the algorithm",
			   }
			  );
      


    my $force=MAINTKEYDB::ARGUMENT::FORCE->new();
    my $kskorzskorboth=MAINTKEYDB::ARGUMENT::KSKORZSKORBOTH->new( );
    my $zones=MAINTKEYDB::ARGUMENT::ZONES->new(
	# Slightly different description and regexp. This is a mandatory
	# zone and more are allowed. Overriding the ZONES Regex will be
	# sufficent.
	Description => '<zone[[ zone]...]>',
	Regexp => '((\S+)(\s+(\S+))*)',
	Default => "",
    );
    my $algorithm=MAINTKEYDB::ARGUMENT::ALGORITHM->new();

    my $size=MAINTKEYDB::ARGUMENT::SIZE->new();

    $self->set_arguments([$force,$kskorzskorboth,$algorithm,$size,$zones]);
}


# method MAINTKEYDB::COMMAND::CREATE::execute_command
# Note these restrictions.

# If no ZSK exists for a given zone the create function will create
# two keys, one marked as "active" (i.e. used for "signing"). This
# allows for the rollover scheme as designed in [1].
#
# If for the given zone and type a key marked as "active" exists the
# create function will not mark newly created keys as "active".
#
# To prevent mistakes that will cause the rollover commands to fail
# the create method will not create a second KSK or a third ZSK unless
# the "force" argument is specified.



sub execute_command {
    my $self=shift;
    my $KeyDB=$self->{_keydb};
    my $logger=get_logger();
    my @attributes= qw (_zones _algorithm _size _type );

    foreach my $attribute ( @attributes ) {
	die "Mandatory $attribute attribute has been found empty in\n".
	  "MAINTKEYDB::COMMAND::CREATE::execute_command()\n".
	    "Contact developer" unless $self->{$attribute};
	print "MAINTKEYDB::COMMAND::CREATE: $attribute: ".
	    $self->{$attribute}."\n" if $self->get_verbose >6;
    }


    my $usedefaults= ($self->{"_size"} eq "default")?1:0;

    my @zones= split (/\s+/,$self->{_zones});
    my $force=0;
    $force=1 unless ($self->{_force} eq "___empty___");

  ZONELOOP:    foreach my $zone (@zones ){
       	my $newkey;
	my @currentkeys=$KeyDB->get_all($zone);
	my $numberofzsk=0;
	my $numberofksk=0;
	foreach my $currentkey (@currentkeys){
	    $numberofzsk++ if $currentkey->is_zsk;
	    $numberofksk++ if $currentkey->is_ksk;
	}
	
	my $numbergenerated=0;
	my $i=$numberofzsk;
	my $numberofkeyscreated=0;

	if (($self->{_type} eq "zsk" || $self->{_type} eq "both" ) &&
	    ($force || $i<2 ))
	{
	    
	    if ($usedefaults){
		if ($self->{"_algorithm"} =~/^rsa/i){
		    $self->{"_size"}= $KeyDB->getconf("rsakeysizezone");
		    $logger->warn( "Using default RSA ZSK Size (".$self->{"_size"}.")\n");
		    print "Using default RSA ZSK Size (".$self->{"_size"}.")\n";
		}elsif($self->{"_algorithm"} =~/^dsa/i){
		    $self->{"_size"}=$KeyDB->getconf("dsakeysizezone");
		    $logger->warn( "Using default DSA ZSK Size (".$self->{"_size"}.")\n");
		    print "Using default DSA ZSK Size (".$self->{"_size"}.")\n";
		    
		}
	    }


	    my $forcezsk=$force;
	    while ($i++<2 || $forcezsk){
		$newkey=$KeyDB->create(
		    $zone,
		    $self->{"_algorithm"},
		    $self->{"_size"},
		    "ZSK"
		) 
		    || $self->error_exit("failed_to_create_key");
#		  print $newkey->description ."\n";

		$numberofkeyscreated++;
		$forcezsk=0;
	    }
	}elsif (($self->{_type} eq "zsk" || $self->{_type} eq "both" ) &&
	    (! $force && $i>2 )){

	    print "No ZSK created for $zone, use \"create force\" to create a key\n";
	    $logger->warn( "No ZSK created for $zone, use \"create force\" to create a key\n");

	}
	if (($self->{_type} eq "ksk" || $self->{_type} eq "both" )&&
	   ( $force || ! $numberofksk ) )
	  {
	    if ($usedefaults){
		if ($self->{"_algorithm"} =~/^rsa/i){
		    $self->{"_size"}=$KeyDB->getconf("rsakeysizekey");
		    $logger->warn( "Using default RSA ZSK Size (".$self->{"_size"}.")\n");
		    print "Using default RSA ZSK Size (".$self->{"_size"}.")\n";
		}elsif($self->{"_algorithm"} =~/^dsa/i){
		    $self->{"_size"}=$KeyDB->getconf("dsakeysizekey");
		    $logger->warn ("Using default DSA ZSK Size (".$self->{"_size"}.")\n");
		    print "Using default DSA ZSK Size (".$self->{"_size"}.")\n";
		}
	    }



	    $newkey=$KeyDB->create(
				   $zone,
				   $self->{"_algorithm"},
				   $self->{"_size"},
				   "KSK"
				  ) 
	      || $self->error_exit("failed_to_create_key");
#	    print $newkey->description ."\n";
	    $numberofkeyscreated++;
	}elsif (($self->{_type} eq "ksk" || $self->{_type} eq "both" ) &&
	    (! $force && $numberofksk )){
	    print "No KSK created for $zone, use \"create force\" to create a key\n";
	    print "   this is a safeguard for unconciously having to many KSKs\n";
	    $logger->warn( "No ZKSK created for $zone, use \"create force\" to create a key\n");

	}
	print " Created $numberofkeyscreated key".
	    (($numberofkeyscreated==1)?'':"s") ." for $zone\n";
	next ZONELOOP;
	return 0;
    }
    
}





######################################################################
#   maintkeydb list ["active"|"inactive"|"published"|"rollover"] ["ksk"|"zsk"] [all|[zone[[ zone] ...]]]
#

package MAINTKEYDB::COMMAND::LIST;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);


@MAINTKEYDB::COMMAND::LIST::ISA = qw(MAINTKEYDB::COMMAND);

# method MAINTKEYDB::COMMAND::LIST::_init
sub _init{
    my ($self,%args)=@_;

    $self->MAINTKEYDB::COMMAND::_init(%args);

    $self->set_prohibitive(
			   {'(zsk|ksk).*(active|inactive|published|rollover)' => 
			    "arguments %s and %s in wrong order",
			   }
			  );



    $self->set_helptext("Purpose: list the keys in the database

You can filter the result by specifying \"active\", \"published\",
\"inactive\" or \"rollover\", by specifying \"ksk\" or \"zsk\" and by
specifying one or more zones.\n" );

    my $active=MAINTKEYDB::ARGUMENT::ACTIVEORINACTIVE->new();
    my $kskorzsk=MAINTKEYDB::ARGUMENT::KSKORZSK->new(
	# This KSK is not mandatory
	Description =>  '["zsk"|"ksk"|"both"]',
	Regexp => '(?i)(zsk|ksk|both)?',
    );
    my @domains=$self->{_keydb}->get_available_domains(1);
    my $zones=MAINTKEYDB::ARGUMENT::ZONES->new(
					       Completion => \@domains,
					       );
    $self->set_arguments([$active,$kskorzsk,$zones]);
}

# method MAINTKEYDB::COMMAND::LIST::execute_command
sub execute_command {
    my $self=shift;
    my $zones; 
    $zones= $self->{_zones} unless ($self->{_zones} eq "___empty___" ||
				    $self->{_zones} eq "all" 
				   );
    my @keys=$self->{_keydb}->get_all($zones);


    return 0 unless @keys;
    foreach my $key (@keys ){
	my $shouldprint=1;
	$shouldprint=0 if $key->is_ksk && $self->{_type} eq "zsk";
	$shouldprint=0 if $key->is_zsk && $self->{_type} eq "ksk";
	$shouldprint=0 if !$key->is_inactive && $self->{_state} eq "inactive";
	$shouldprint=0 if !$key->is_active && $self->{_state} eq "active";
	$shouldprint=0 if !$key->is_published && $self->{_state} eq "published";
	$shouldprint=0 if !$key->is_rollover && $self->{_state} eq "rollover";
	print $key->description. "\n" if $shouldprint;
    }
    return 0;
}



######################################################################
#   maintkeydb delete_id  <zone> <algorithm> <keyID[[ keyID] ...]> 
#

package MAINTKEYDB::COMMAND::DELETE_ID;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::COMMAND::DELETE_ID::ISA = qw(MAINTKEYDB::COMMAND);

sub _init{
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);
    my @domains=$self->{_keydb}->get_available_domains(1);
    my $zone=MAINTKEYDB::ARGUMENT::ZONE->new(
					     Completion => \@domains,
					    );
    my $algorithm=MAINTKEYDB::ARGUMENT::ALGORITHM->new();
    my $keyid=MAINTKEYDB::ARGUMENT::KEYID->new();
    $self->set_arguments([$zone,,$algorithm,$keyid]);

    $self->set_helptext("Purpose: Delete keys by keyid

Zone-Algorithm-KeyID uniquely defines a key. Use the  list <zone> to 
identify the keys. In shell mode command-line completion [tab] will show
you the available KeyIDs.\n");

}

# method MAINTKEYDB::COMMAND::DELETE_ID::execute_command
sub execute_command {
    my $self=shift;
    my $key=$self->{_keydb};
    
    my @attributes= qw (_zones _algorithm _keyid );
    foreach my $attribute ( @attributes ) {
	die "Mandatory $attribute attribute has been found empty in\n".
	  "MAINTKEYDB::COMMAND::DELETE_ID::execute_command()\n".
	    "Contact developer" if ( ! $self->{$attribute} ||
				      $self->{$attribute} eq "___empty___") ;
    }
    my @keyids=split /\s+/,$self->{_keyid};

    my $deletedkeys=0;
    KEYIDLOOP: foreach my $keyid (@keyids){
	my $result=$key->fetch($self->{_zones},$self->{_algorithm},$keyid);
	if ($result){
	    print $result ."\n" if $self->get_verbose;
	    next KEYIDLOOP;
	}

	$self->fh->print ("Deleting: ".$key->description ."\n" );
	$key->deletekey;
	$deletedkeys++;

    }

    return $self->exit_error("no_keys_deleted") if ! $deletedkeys;
    return 0;
}


######################################################################
#  maintkeydb delete_name  <"ksk"|"zsk"|"both">  <algorithm> <zone[[ zone],...]> 
#
package MAINTKEYDB::COMMAND::DELETE_NAME;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::COMMAND::DELETE_NAME::ISA = qw(MAINTKEYDB::COMMAND);

sub _init{
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);
    my $kskorzskorboth=MAINTKEYDB::ARGUMENT::KSKORZSKORBOTH->new();
    my $algorithm=MAINTKEYDB::ARGUMENT::ALGORITHM->new();


    my @domains=$self->{_keydb}->get_available_domains(1);

    my $zones=MAINTKEYDB::ARGUMENT::ZONES->new(
	# Slightly different description and regexp. This is a mandatory
	# zone and more are allowed. Overriding the ZONES Regex will be
	# sufficent.
	       Completion => \@domains,
	       Description => '<zone[[ zone]...>',
	       Regexp => '((\S+)(\s+(\S+))*)',
	       Default => "",

    );
    $self->set_arguments([$kskorzskorboth,$algorithm,$zones]);

    $self->set_helptext("Purpose: Delete keys by name

To delete the KSK, the ZSK or just all keys for given algorithm and zones. Be careful
you will not be asked for confirmation.

Specfying at least one zone is mandatory.
"
);

}

# method MAINTKEYDB::COMMAND::DELETE_NAME::execute_command
sub execute_command {
    my $self=shift;
    my $KeyDB=$self->{_keydb};
    
    my @attributes= qw (_zones  _type _algorithm );
    foreach my $attribute ( @attributes ) {
	die "Mandatory $attribute attribute has been found empty in\n".
	  "MAINTKEYDB::COMMAND::DELETE_NAME::execute_command()\n".
	    "Contact developer" if ( ! $self->{$attribute} ||
				      $self->{$attribute} eq "___empty___") ;
    }
    my @keys=$KeyDB->get_all($self->{_zones});
    if (! @keys ){
	print "No keys found for ".$self->{_zones}."\n" if $self->get_verbose;
	return (0);
    }
    foreach my $key (@keys){
	if  ($key->is_algorithm($self->{_algorithm}) &&
	      ( ($self->{_type} eq "both")||
		($key->is_ksk && ($self->{_type} eq "ksl")) ||
		($key->is_zsk && ($self->{_type} eq "zsl"))
		)
	      )
	{
	$self->fh->print ("Deleting: ".$key->description ."\n" );
	$key->deletekey;
	}
    }
}



######################################################################
#
#  maintkeydb state  <"active"|"inactive"|"published"> <zone> <algorithm> <keyID[[ keyID] ...]> 
#
package MAINTKEYDB::COMMAND::STATE;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::COMMAND::STATE::ISA = qw(MAINTKEYDB::COMMAND);
sub _init {
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);
    my $state=MAINTKEYDB::ARGUMENT::STATE->new();
    my @domains=$self->{_keydb}->get_available_domains(1);

    my $zone=MAINTKEYDB::ARGUMENT::ZONE->new(
	       Completion => \@domains,
				      );
    my $algorithm=MAINTKEYDB::ARGUMENT::ALGORITHM->new();
    my $keyid=MAINTKEYDB::ARGUMENT::KEYID->new();
    $self->set_arguments([$state,$zone,$algorithm,$keyid]);
    
    $self->set_helptext("Purpose: Change the state of a key

A key can be active (published in the zone and used for signing),
published (published in the zone but not used for signing), or
inactive (nor published in the zone nore used for signing).

At the risk of not being able to perform the rollover command you can
use the state command to change the state of individual keys.\n");

}


# method MAINTKEYDB::COMMAND::STATE::execute_command
#
# States the state of the keys 
#
sub execute_command {
    my $self=shift;
    my $key=$self->{_keydb};

    my @attributes= qw (_zones  _state _keyid  _algorithm );
    foreach my $attribute ( @attributes ) {
	die "Mandatory $attribute attribute has been found empty in\n".
	  "MAINTKEYDB::COMMAND::STATE::execute_command()\n".
	    "Contact developer" if ( ! $self->{$attribute} ||
				      $self->{$attribute} eq "___empty___") ;
    }

    my @keyids=split /\s+/,$self->{_keyid};
    KEYIDLOOP: foreach my $keyid (@keyids){
	my $result=$key->fetch($self->{_zones},$self->{_algorithm},$keyid);
	if ($result){
	    print $result ."\n" if $self->get_verbose;
	    next KEYIDLOOP;
	}

	print "State: ".$key->description ." set to " if $self->get_verbose;
	if ( lc ($self->{_state}) eq "active"){
	    print  " ACTIVE\n" if $self->get_verbose;
	    $key->set_active;
	}elsif ( lc ($self->{_state}) eq "inactive"){
	    print  " INACTIVE\n" if $self->get_verbose;
	    $key->set_inactive;
	}elsif ( lc ($self->{_state}) eq "published"){
	    print  " PUBLISHED\n" if $self->get_verbose;
	    $key->set_published;
	}else {
	    $self->fh->print( " unkown state (no action)") if $self->get_verbose;
	    return $self->exit_error("state_attribute_malformed");
	}
	die "MAINTKEYDB::COMMAND::STATE state change failed" if lc($key->state)
	  ne lc ($self->{_state});
	
	
    }
    return 0;
    
}
######################################################################
#
#   mainkeydb rollover <"ksk-stage1"|"ksk-stage2"|"zsk-stage1"|"zsk-stage2"> <algorithm>\
#    <zone[[ zone]...]>
#

package MAINTKEYDB::COMMAND::ROLLOVER;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
import Net::DNS::SEC::Maint::Key qw(oldest_key);

@MAINTKEYDB::COMMAND::ROLLOVER::ISA = qw(MAINTKEYDB::COMMAND);
sub _init {
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);

    my @domains=$self->{_keydb}->get_available_domains(1);

    my $zones=MAINTKEYDB::ARGUMENT::ZONES->new(
	# Slightly different description and regexp. This is a mandatory
	# zone and more are allowed. Overriding the ZONES Regex will be
	# sufficent.
					     
        Completion => \@domains,
	Description => '<zone[[ zone]...>',
	Regexp => '((\S+)(\s+(\S+))*)',
	Default=> "",
    );
    my $stage=MAINTKEYDB::ARGUMENT::STAGE->new();
    my $algorithm=MAINTKEYDB::ARGUMENT::ALGORITHM->new();
    $self->set_arguments([$stage,$algorithm,$zones]);
    $self->set_helptext("Purpose: perform an automatic rollover
Given that the key database is in the appropriate state one can perform
a rollover using this command.

Hint: If you list a set of keys and they are tagged with (r) you will need a
\"stage2\" rollover.
");
}


sub execute_command {
    my $self=shift;
    my $keydb=$self->{_keydb};

    my @attributes= qw (_stage  _algorithm _zones );
    foreach my $attribute ( @attributes ) {
	die "Mandatory $attribute attribute has been found empty in\n".
	  "MAINTKEYDB::COMMAND::STATE::execute_command()\n".
	    "Contact developer" if ( ! $self->{$attribute} ||
				      $self->{$attribute} eq "___empty___") ;
    }
    my @zones=split /\s+/,$self->{_zones};
    my $stage=$self->{_stage}; 
    my $algorithm=uc $self->{_algorithm};
    #
    # Check if for all zones the prerequisits are met.
    # If not print to STDERR and exit
    my $prereqfailed=0;
  PRECHECK:    foreach my $zone (@zones ){
	my @activekeys;
	my @inactivekeys;
	my @publishedkeys;
	my @rolledkeys;

	# Test the prerequisits for a zsk rollover
	# For stage 1 you need to have one published and one active key.
	# For stage 2 you need to have one published key in state Rollover
	#             and one active key.
	if ($stage =~/zsk-stage(1|2)/){
	    my $substage=$1;
	    @activekeys=$keydb->get_active_zone($zone,
						$algorithm);
	    @inactivekeys=$keydb->get_inactive_zone($zone,
						    $algorithm);
	    @publishedkeys=$keydb->get_published_zone($zone,
						      $algorithm);
	    @rolledkeys=$keydb->get_rollover_zone($zone,
						  $algorithm);
	    if (@activekeys != 1){
		$self->fh->print ("Only 1 active ZSK allowed, you have:". @activekeys ." active ZSKs for $zone ($algorithm)\n");
		foreach my $k (@activekeys){
		    print STDERR $k->description ."\n";
		}
		$prereqfailed=1;
	    }
	    if (@publishedkeys != 1){
		$self->fh->print ("Only 1 published ZSK allowed, you have:". @publishedkeys ." published ZSKs for $zone ($algorithm)\n");
		$prereqfailed=1;
	    }
	    if ($substage==1 && @rolledkeys ){
		$self->fh->print ( "There is a key marked as being rolled\nYou probably want to run zsk-stage2 for $zone ($algorithm)\n");
		$prereqfailed=1;
	    }
	    if ($substage==2 && @rolledkeys==0 ){
		$self->fh->print ("No keys marked for rollover, cannot do zsk-stage2 for $zone ($algorithm)\n");
		$prereqfailed=1;
	    }


	    if ($substage==2 && @rolledkeys==1 && !$rolledkeys[0]->is_published ){
		$self->fh->print( "THIS IS SERIOUS\n");
		$self->fh->print ( "No the key marked for rollover is not published.$zone ($algorithm)\n");
		 $self->fh->print ( "either delete or change the state of the key\n");
		
		$self->fh->print ( $rolledkeys[0]->description);
		$prereqfailed=1;
	    }
	    if ( @rolledkeys>1 ){
		$self->fh->print ( "THIS IS SERIOUS\n");
		$self->fh->print ( "THERE ARE TO MANY KEYS FOR $zone ($algorithm) THAT ARE ARE INDICATED THAT THEY ARE ROLLED\n");
		$self->fh->print ( "You probably want to delete one of them:\n");
		foreach my $roll (@rolledkeys){
		    $self->fh->print ( $roll->description ."\n");
		}
		return $self->exit_error("inconsistent_rollover_setting");
	    }


	#
	# Test the prerequisits for a ksk rollover
	# For stage 1 you need to have one active key, non marked for rollover.
	    
	# For stage 2 you need to have two active keys, one of wich is marked
	# for rollover.
	}elsif($stage=~/ksk-stage(1|2)/){
	    my $substage=$1;
	    @activekeys=$keydb->get_active_key($zone,
					       $algorithm);
	    @inactivekeys=$keydb->get_inactive_key($zone,
						   $algorithm);
	    @publishedkeys=$keydb->get_published_key($zone,
						     $algorithm);
	    @rolledkeys=$keydb->get_rollover_zone($zone,
						  $algorithm);

	    my @keysinrollover;

	    foreach my $ak (@activekeys){
		push @keysinrollover, $ak if $ak->is_rollover;
		
	    }



	    if ($substage==1 && @activekeys<1 ){
		$self->fh->print ( "KSK rollover stage1 will only work if there is more than one active key, now there are ".@activekeys. " for $zone ($algorithm)\n");
		$prereqfailed=1;
	    }

	    if ($substage==1 && @activekeys==1 && $activekeys[0]->is_rollover ){
		$self->fh->print ( "THIS IS SERIOUS!\n");
		$self->fh->print ( "The only active ksk available is marked as being in a rollover.\n");
		$self->fh->print ( "You have probably mannually deleted the wrong key.\n");
		$self->fh->print ( "You will need to hack the database for $zone ($algorithm)\n");
		
		$prereqfailed=1;
	    }



	    if ($substage==1 && @keysinrollover ){
		$self->fh->print ( "You called ksk-stage1 while there are keys marked for rollover\n");
		$self->fh->print ( "You probably wanted to perform a ksk-stage2 rollover\n");
		
		$prereqfailed=1;
	    }


	    if ( @keysinrollover > 1 ){
		$self->fh->print ( "You have more than one key marked for rollover.\n");
		$self->fh->print ( "You have probably mannually set the rollover attribute or something weired happened\n");
		$self->fh->print ( "You will need to hack the database for $zone ($algorithm)\n");
		$self->fh->print ( "If your parend does not have a DS RR pointing to the keys marked in rollover you can\n");
		$self->fh->print ( "safely delete these.");
		
		$prereqfailed=1;
	    }		
		
	    if ($substage==2 && @activekeys<2){

		if (@activekeys < 2){
		    $self->fh->print ( "KSK rollover stage2 will only work if there are 2 or more active key, now there are  ".@activekeys. " active keys in $zone ($algorithm)\n");
		    $prereqfailed=1;
		}
	    }
	    if ($substage==2 && @activekeys>=2){
		my $oldest=oldest_key( @activekeys);
		if(
		    ! ($oldest->is_rollover )
		){
		    $self->fh->print ( "The oldest key should be in  rollover state\n");
		    $self->fh->print ( "You cannot do a ksk-stage2 rollover for $zone($algorithm)\n");
		    $prereqfailed=1;
		}
	    }

	}else{
	    die 
	      "MAINTKEYDB::COMMAND::STATE::execute_command()\n".
		"hitting the wrong part of a case switch\n".
		  "Contact developer\n";
      }

    }

    return $self->exit_error("rollover_prerequisits_not_met") if $prereqfailed;

DOROLLOVER:    foreach my $zone (@zones ){
	if ($self->{_stage} =~ /ksk-stage(1|2)/){
	    my $err=$keydb->rollover_key($zone,$algorithm);
	    if ($err){
		$self->fh->print ( "Rollover of KSK for $zone ($algorithm) failed: \n");
		$self->fh->print ( $err ."\n");
		
		$self->fh->print ( "Not all ksk's in " .join(" ",@zones).
		  " may have been rolled" )if @zones >1;
		return $self->exit_error("rollover_of_ksk_failed");
	    }
	}
	
	if ($self->{_stage} =~ /zsk-stage(1|2)/){
	    my $err=$keydb->rollover_zone($zone,$algorithm);
	    if ($err){
		$self->fh->print ( "Rollover of ZSK for $zone ($algorithm) failed: \n");
		$self->fh->print ( $err ."\n");
		
		$self->fh->print ( "Not all zsk's in " .join(" ",@zones).
		  " may have been rolled") if @zones >1;
		return $self->exit_error("rollover_of_zsk_failed");
	    }
	}
    } #END DOROLLOVER
}


######################################################################
#
#   maintkeydb showkey ["ds"] [zone[[ zone],...]]
#
package MAINTKEYDB::COMMAND::SHOWKEYS;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);


@MAINTKEYDB::COMMAND::SHOWKEYS::ISA = qw(MAINTKEYDB::COMMAND);
sub _init {
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);


    my @domains=$self->{_keydb}->get_available_domains(1);

    my $zones=MAINTKEYDB::ARGUMENT::ZONES->new(
        Completion => \@domains,
					       );

    my $ds=MAINTKEYDB::ARGUMENT::DS->new();
    $self->set_arguments([$ds,$zones]);
}


sub execute_command {
    my $self=shift;
    my $keydb=$self->{_keydb};

    my @attributes= qw (_zones _ds);
    foreach my $attribute ( @attributes ) {
	die "Mandatory $attribute attribute has been found empty in\n".
	  "MAINTKEYDB::COMMAND::STATE::execute_command()\n".
	    "Contact developer" if ( ! $self->{$attribute} );
    }

    # if-then-else in bla?foo:dot; disguise
    my @zones= ($self->{_zones} eq "___empty___")?
      $keydb->get_available_domains(1) :
        split /\s+/,$self->{_zones} ;



    foreach my $zone (@zones){
	my @keys;
	if ($self->{_ds} ne "ds"){
	    @keys=($keydb->get_published($zone),
		   $keydb->get_active($zone));
	}else{
	    @keys=($keydb->get_published_key($zone),
		   $keydb->get_active_key($zone));
	}	    
	print "\n;;;;;; DNSKEY RRs for $zone ;;;;;;;;;\n";
	foreach my $key (@keys){
	    print $key->keyrrstring ;
	    print " KSK" if $key->is_ksk;
	    print " ZSK" if $key->is_zsk;
	    print " (to be depricated)" if $key->is_rollover;
	    print " (upload to parent)" if ! $key->is_rollover && $key->is_ksk;

	    print "\n";
	}


	if ($self->{_ds} eq "ds"){
	    print "\n;;;;;; DS RRs for $zone ;;;;;;;;;\n";
	    foreach my $key (@keys){
		my $keyrr=Net::DNS::RR->new($key->keyrrstring);
		my $dsrr=Net::DNS::RR::DS->create($keyrr);
		print "; The next DS RR is an old one, it is to disappear\n" if $key->is_rollover;
		$dsrr->string=~/^(.*)\;.*$/;
		print $1 ."\n";
	    }
	}
	


    }
}




######################################################################
#
#   maintkeydb parentdata <ds|key|both> [zone[[ zone],...]]
#
package MAINTKEYDB::COMMAND::PARENTDATA;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);


@MAINTKEYDB::COMMAND::PARENTDATA::ISA = qw(MAINTKEYDB::COMMAND);
sub _init {
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);


    my @domains=$self->{_keydb}->get_available_domains(1);

    my $zones=MAINTKEYDB::ARGUMENT::ZONES->new(
        Completion => \@domains,
					       );

    my $dsorkey=MAINTKEYDB::ARGUMENT::DSORKEY->new();

    $self->set_arguments([$dsorkey,$zones]);

}


sub execute_command {
    my $self=shift;
    my $keydb=$self->{_keydb};
    
    my @attributes= qw (_zones _dsorkey);
    foreach my $attribute ( @attributes ) {
	die "Mandatory $attribute attribute has been found empty in\n".
	    "MAINTKEYDB::COMMAND::STATE::execute_command()\n".
	    "Contact developer" if ( ! $self->{$attribute} );
    }
    


    my @zones= (($self->{_zones} eq "___empty___")||
	($self->{_zones} eq "all" )) ?
	$keydb->get_available_domains(1) :
	split /\s+/,$self->{_zones} ;
    
    foreach my $zone (@zones){
	my @keys;
        @keys=($keydb->get_published_key($zone),
	       $keydb->get_active_key($zone));

	if ($self->{_dsorkey} eq "key" ||$self->{_dsorkey} eq "both" ){
	    foreach my $key (@keys){
		print $key->keyrrstring ."\n" unless $key->is_rollover;
	    }
	}
	if ($self->{_dsorkey} eq "ds" ||$self->{_dsorkey} eq "both"){
	    foreach my $key (@keys){
		next if $key->is_rollover;
		my $keyrr=Net::DNS::RR->new($key->keyrrstring);
		my $dsrr=Net::DNS::RR::DS->create($keyrr);
		$dsrr->string=~/^(.*)\;.*$/;
		print $1 ."\n";
	    }
	}
    }
}





######################################################################
#
#   maintkeydb help
#
package MAINTKEYDB::COMMAND::HELP;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::COMMAND::HELP::ISA = qw(MAINTKEYDB::COMMAND);
sub _init {
    my ($self,%args)=@_;
    $self->MAINTKEYDB::COMMAND::_init(%args);
    $self->set_arguments([]);
}


sub execute_command {
    my $self=shift;
    $self->fh->print (<<ENDTEXT);

  Usage:
        maintkeydb -h
	maintkeydb [-v verbose] [-c configfile] [-i] <commands> <arguments>

  maintkeyd is a frontend to a database of dnssec keys. 

  You can use it as a command line interface or, with the -i flag, as a
  shell interface.

  The shell interface has a history mechanism and [TAB] based command
  line completion.

  The following commands are available: create, delete_name, delete_id,
  help, list, rollover, showkeys and state.

  For help on individual commands type <command> help in the shell or
  maintkeydb <command> help from the command line interface.

ENDTEXT

    
}
#########################################################################
#
#  MAINTKEYDB::ARGUMENT
#
#########################################################################
package MAINTKEYDB::ARGUMENT;
use Carp;
use Log::Log4perl qw(get_logger :levels);
use vars qw($AUTOLOAD @ISA);
@MAINTKEYDB::ARGUMENT::ISA = qw(MAINTKEYDB);


sub AUTOLOAD {
    my $self=shift;
    my $arg=shift;
    $AUTOLOAD =~/.*::(get|set)_(\w+)/
      or croak "No such method: $AUTOLOAD\;";
    my $attribute="_".$2;
    if ($1 eq "set"){
	$self->{$attribute}=$arg if defined $arg;	
    }
    return $self->{$attribute} ;
}



# method MAINTKEYDB::ARGUMENT::new
sub new {
    my ($class,%args)=@_;
    my $self=bless {}, ref($class)||$class;   # How simple...
    $self->_init(%args);
    return $self;
}

# method MAINTKEYDB::ARGUMENT::_init
sub _init {
    my ($self,%args)=@_;
    $self->{_description} = $args{Description};
    $self->{_regexp} = $args{Regexp};
    $self->{_attributename} = $args{Attributename};
}

# method MAINTKEYDB::ARGUMENT::get_askregexp
# Will return _askregexp argument if available otherwise
# it will get the regexp attribute.
sub get_askregexp {
    my $self=shift;
    return $self->{_askregexp} if $self->{_askregexp};
    $self->get_regexp;
}

# method MAINTKEYDB::ARGUMENT::init_askarg
# Purpose: initialize some ARGUMENT attribute before proceding into
# asking the arguments. See for instance MAINTKEYDB::ARGUMENT::KEYID
# this one is a cathcer so that we do not end up at the AUTOLOAD
#
# Should have a command as argument;
# should return a non-emty string  if there are no arguments to ask for or
# failure occured

sub init_askarg {
    return 0;
    }

# method MAINTKEYDB::ARGUMENT::askarg
# Purpose: initiate a question response with the user to get 
# required data
sub askarg {
    my $self= shift;
    my $command=shift;

    die "argument is not a MAINTKEYDB::COMMAND object" unless $command->isa("MAINTKEYDB::COMMAND");
    my $fh=$command->fh;
    my $term=$command->{_term};

    my $t_attr=$term->Attribs;
    my $gotnothing=1;
    
    if ( my $err=$self->init_askarg($command) ){
	return $err;
    }    
    
    $fh->print ($self->get_requesttext."\n") if $self->get_requesttext;
    $t_attr->{completion_word} = $self->get_completion?
      $self->get_completion:[];
    my $inline;
  READLINE:    while ($gotnothing){
	$inline=$term->readline($self->get_prompt?$self->get_prompt:"> ",
				   $self->get_default?$self->get_default:"",
				  );


	next READLINE if ! defined $inline ;
	return "exit from argument challenge-response"  if $inline=~/^\s*exit\s*$/;
	my $regexp='^\s*'.$self->get_askregexp();
	$fh->print ("REGEXP: --$inline--".$regexp ."\n" )if $self->get_verbose >5;
	if ( $inline=~ s/($regexp)//x ){
	    $command->{$self->get_attributename()}=(lc $1 && $1 ne "skip")?
	      $1:
	      "___empty___";
	    $gotnothing=0;
	}
	
	
    }
    $fh->print  ("GOT: ".$command->{$self->get_attributename()}." for ".$self->get_attributename() ."\n") if $self->get_verbose > 5;
    return 0;
}

# Class of zones argument 
# All that is needed to parse   '[zone[[ zone]...]';
# Note: non-mandatory, possible multiple argument
package MAINTKEYDB::ARGUMENT::ZONES;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::ARGUMENT::ZONES::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description} = $args{Description}|| '[all| [[zone[[ zone]...]]';
    $self->{_regexp}= $args{Regexp} || '((\ball\b)|((\S+)?(\s+(\S+))*))';
    $self->{_attributename}= $args{Attributename} ||"_zones";

    $self->{_requesttext}= $args{Requesttext} ||"Enter one or more zone names";
    $self->{_completion}= $args{Completion} ||[];
    $self->{_prompt}= $args{Prompt} ||"zone(s) > ";
    $self->{_default}= $args{Default} || defined $args{Default}?"":"all";
}



# Class of zone argument 
# All that is needed to parse   '<zone>';
# Note: mandatory, only one argument
package MAINTKEYDB::ARGUMENT::ZONE;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::ARGUMENT::ZONE::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description} = $args{Description}|| '<zone>';
    $self->{_regexp}= $args{Regexp} || '(\S+)';
    $self->{_attributename}= $args{Attributename} ||"_zones";
    $self->{_requesttext}= $args{Requesttext} ||"Enter a zone name";
    $self->{_completion}= $args{Completion} ||[];
    $self->{_prompt}= $args{Prompt} ||"zone > ";
    $self->{_default}= $args{Default} ||"";

}




# Class of zone argument 
# All that is needed to parse   '["ds"]';
package MAINTKEYDB::ARGUMENT::DS;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::ARGUMENT::DS::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description} = $args{Description}|| '[ds]';
    $self->{_regexp}= $args{Regexp} || '(\bds\b)?';
    $self->{_attributename}= $args{Attributename} ||"_ds";
}


# Class of zone argument 
# All that is needed to parse   '<"ds"|"key"|"both">';
package MAINTKEYDB::ARGUMENT::DSORKEY;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);

@MAINTKEYDB::ARGUMENT::DSORKEY::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description}=$args{Description} || '<"key"|"ds"|"both">';
    $self->{_regexp}=$args{Regexp} || '(?i)(key|ds|both)';
    $self->{_attributename}=$args{Attributename} || "_dsorkey";
    $self->{_requesttext}= $args{Requesttext} ||"DS or KEY?";
    $self->{_completion}= $args{Completion} ||["ds","key","both"];
    $self->{_askregexp}=$args{Askregexp} || '(?i)(\b(ds|key|both)\b)';
    $self->{_prompt}= $args{Prompt} ||"ds or key > ";
    $self->{_default}= $args{Default} ||"both";

}



# Class of force argument 
# All that is needed to parse   '["force"]';
# Note: non-mandatory, possible multiple argument
package MAINTKEYDB::ARGUMENT::FORCE;
use vars qw($AUTOLOAD @ISA);
@MAINTKEYDB::ARGUMENT::FORCE::ISA = qw(MAINTKEYDB::ARGUMENT);
use Log::Log4perl qw(get_logger :levels);

sub _init {
    my ($self,%args)=@_;
    $self->{_description} = $args{Description} || '["force"]';
    $self->{_regexp} = $args{Regexp} || '((?i)(\bforce\b)?)';
    $self->{_attributename}= $args{Attributename} || "_force";
    $self->{_requesttext}= $args{Requesttext} ||"Force creation?";
    $self->{_completion}= $args{Completion} ||["skip","force"];
    $self->{_prompt}= $args{Prompt} ||"[skip|force] > ";
    $self->{_default}= $args{Default} ||"skip";
    $self->{_askregexp} = $args{Askregexp} || '((?i)\b(force|skip)\b)';

}


# Class of size argument 
# All that is needed to parse   '<size>';
# Note: non-mandatory, possible multiple argument
package MAINTKEYDB::ARGUMENT::SIZE;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::SIZE::ISA = qw(MAINTKEYDB::ARGUMENT);


sub _init {
    my ($self,%args)=@_;
    $self->{_description} = $args{Description} || '<size>';
    $self->{_regexp} = $args{Regexp} || '(\d+|default)';
    $self->{_attributename}= $args{Attributename} || "_size";

    $self->{_requesttext}= $args{Requesttext} ||"Key Size?";
    $self->{_completion}= $args{Completion} ||["1024","2048"];
    $self->{_prompt}= $args{Prompt} ||"keysize > ";
    $self->{_default}= $args{Default} ||"default";


}
# Class of algorithm argument
# All that is needed to parse <RSASHA1|DSA|RSA|RSAMD5>  
package MAINTKEYDB::ARGUMENT::ALGORITHM;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::ALGORITHM::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description}=$args{Description} || '<RSASHA1|DSA|RSA|RSAMD5>';
    $self->{_regexp}=$args{Regexp} || '(?i)(\b(RSASHA1|DSA|RSA|RSAMD5)\b)';
    $self->{_attributename}=$args{Attributename} || "_algorithm";
    $self->{_requesttext}= $args{Requesttext} ||"Algorithm?";
    $self->{_completion}= $args{Completion} ||["DSA","RSASHA1","RSA","RSAMD5"];
    $self->{_prompt}= $args{Prompt} ||"algorithm > ";
    $self->{_default}= $args{Default} ||"RSASHA1";
}



# Class of algorithm argument
# All that is needed to parse '["zsk"|"ksk"]';
# note non-mandatory also see MAINTKEYDB::ARGUMENT::KSKORZSKORBOTH;

package MAINTKEYDB::ARGUMENT::KSKORZSK;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::KSKORZSK::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description}=$args{Description} || '["zsk"|"ksk"]';
    $self->{_regexp}=$args{Regexp} || '(?i)(zsk|ksk)?';
    $self->{_attributename}=$args{Attributename} || "_type";
    $self->{_requesttext}= $args{Requesttext} ||"KSK or ZSK?";
    $self->{_completion}= $args{Completion} ||["ksk","zsk","both"];
    $self->{_askregexp}=$args{Askregexp} || '(?i)(zsk|ksk|both)';
    $self->{_prompt}= $args{Prompt} ||"type > ";
    $self->{_default}= $args{Default} ||"both";

}




# Class of algorithm argument
# All that is needed to parse '<"active"|"inactive"|"published">'
# NOTE mandatory includes "both" as option
package MAINTKEYDB::ARGUMENT::STATE;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::STATE::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description}=$args{Description} || 
                        '<"active"|"inactive"|"published">';
    $self->{_regexp}=$args{Regexp} || '(?i)(\b(active|inactive|published)\b)';
    $self->{_attributename}=$args{Attributename} || "_state";

    $self->{_requesttext}= $args{Requesttext} ||"active, inactive or published keys?";
    $self->{_completion}= $args{Completion} ||["active","inactive","published"];
    $self->{_prompt}= $args{Prompt} ||"state > ";
    $self->{_default}= $args{Default} ||"";
}





# Class of algorithm argument
# All that is needed to parse '<"zsk"|"ksk"|"both">';
# NOTE mandatory includes "both" as option
# also see MAINTKEYDB::ARGUMENT::KSKORZSK
package MAINTKEYDB::ARGUMENT::KSKORZSKORBOTH;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::KSKORZSKORBOTH::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description}=$args{Description} || '<"zsk"|"ksk"|"both">';
    $self->{_regexp}=$args{Regexp} || '(?i)(\b(zsk|ksk|both)\b)';
    $self->{_attributename}=$args{Attributename} || "_type";
    $self->{_requesttext}= $args{Requesttext} ||"Create a KSK, a ZSK or both?";
    $self->{_completion}= $args{Completion} ||["ksk","zsk","both"];
    $self->{_prompt}= $args{Prompt} ||"type > ";
    $self->{_default}= $args{Default} ||"both";
}




# Class of keyid argument 
# All that is needed to parse  '<keyID[[ keyID] ...]>';
package MAINTKEYDB::ARGUMENT::KEYID;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::KEYID::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description} = $args{Description} || '<keyID[[ keyID] ...]>';
    $self->{_regexp} = $args{Regexp} || '((\d+)(\s+\d+)*)';
    $self->{_attributename}= $args{Attributename} || "_keyid";

    $self->{_requesttext}= $args{Requesttext} ||"Enter one or more KeyIDs?";
    $self->{_completion}= $args{Completion} ||[];
    $self->{_prompt}= $args{Prompt} ||"keyID > ";
    $self->{_default}= $args{Default} ||"";

}

# methode MAINTKEYDB::ARGUMENT::KEYID::init_askarg;
# Purpose: initialize some ARGUMENT attribute before proceding into
# asking the arguments.
# Here we try to get the available key_ids for a zone so we have them 
# for command line completion
#
# Should have a command as argument;

sub init_askarg {
    my $self=shift;
    my $command=shift;
    my $zone; my $algorithm ;
    if ( ($zone=$command->get_zones) && ($algorithm=$command->get_algorithm) ){
	my @algoid_pairs=
	my @algos=$command->{_keydb}->get_available_keyids($algorithm,$zone);
	$self->{_completion}=\@algos;
	return 0 if @algos
    }
    return "No keys for this zone/algorithm combination";
}

# Class of algorithm argument
# All that is needed to parse '["active"|"inactive"|"published"|"rollover"]
package MAINTKEYDB::ARGUMENT::ACTIVEORINACTIVE;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::ACTIVEORINACTIVE::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description}=$args{Description} || '["active"|"inactive"|"published"|"rollover"|"allstates"]';
    $self->{_regexp}=$args{Regexp} || '((?i)\b(active|inactive|published|rollover|allstates)\b)?';
    $self->{_attributename}=$args{Attributename} || "_state";

    $self->{_askregexp}=$args{Askregexp} || '((?i)\b(active|inactive|published|rollover|allstates)\b)';

    $self->{_requesttext}= $args{Requesttext} ||"active, inactive, rollover or published keys?";
    $self->{_completion}= $args{Completion} ||["active","inactive","published","rollover","allstates"];
    $self->{_prompt}= $args{Prompt} ||"state > ";
    $self->{_default}= $args{Default} ||"allstates";

}



# Class of algorithm argument
# All that is needed to parse '<"ksk-stage1"|"ksk-stage2"|"zsk-stage1"|"zsk-stage2">'
package MAINTKEYDB::ARGUMENT::STAGE;
use vars qw($AUTOLOAD @ISA);
use Log::Log4perl qw(get_logger :levels);
@MAINTKEYDB::ARGUMENT::STAGE::ISA = qw(MAINTKEYDB::ARGUMENT);

sub _init {
    my ($self,%args)=@_;
    $self->{_description}=$args{Description} || '<"ksk-stage1"|"ksk-stage2"|"zsk-stage1"|"zsk-stage2">';
    $self->{_regexp}=$args{Regexp} ||'(?i)\b(ksk-stage1|ksk-stage2|zsk-stage1|zsk-stage2)\b';
    $self->{_attributename}=$args{Attributename} || "_stage";


    $self->{_requesttext}= $args{Requesttext} ||"Enter rollover stage?";
    $self->{_completion}= $args{Completion} ||["ksk-stage1","ksk-stage2","zsk-stage1","zsk-stage2"];
    $self->{_prompt}= $args{Prompt} ||"rollover stage > ";
    $self->{_default}= $args{Default} ||"";


}



#
######################################################################
#
#  This is where the processing happens.
#  main() so to speak
#
##############################################################
#
# Create initial MAINTKEYDB instance.
#

package MAINTKEYDB;
#Back into the original namespace again.

#
# commandline arguments first.




my %options;
getopts('c:Dhiv:', \%options); # read the options with getopts


# Overwrite the DNSSECMAINT_CONFFILE variable if "-c <confile> is specified.
    if ( $options{c} ){
     my $confpath=$options{c};
     if ([ -f $confpath]){
	 $ENV{"DNSSECMAINT_CONFFILE"}=$confpath;
     }else{
	 print STDERR "$confpath does not exist; using default configuration!\n";
     }
 }



#
# The D option is only to be used for development purposes. It sets some
# paths relevant in _my_ develoment environment. Hence it remains undocumente.
#
if ( $options{D} ){
    $ENV{"DNSSECMAINT_DNSSEC_KEYGEN"}= "/sw/bin/openssl rand -out development_tests/random 100000 ;/usr/local/sbin/dnssec-keygen -r development_tests/random ";	
    $ENV{"DNSSECMAINT_CONFFILE"}="development_tests/dnssecmaint.conf";
}



my $ui=MAINTKEYDB->new;

if ($options{h}){
	my %argument=%{$ui};
	my $helpinst=MAINTKEYDB::COMMAND::HELP->_new(%argument);
	$helpinst->execute_command;
	exit 0;
    }




$ui->{_term}=0;


my $fh=IO::Handle->new();


if ($options{i}){
    my $term;
    if ( -t STDIN ) {
        $term = Term::ReadLine::Gnu->new("MAINTKEYDB");
        $SIG{INT} = sub { print "Next time use 'exit' to quit\n"; exit; };

    } else {
        $term = 0;
    }
    $ui->{_term}=$term;
    $fh->fdopen(fileno(STDOUT),"w")|| die "Could not opens STDOUT";
    $ui->fh($fh);   # Default FH is STDOUT
}else{
    $fh->fdopen(fileno(STDERR),"w")|| die "Could not opens STDERR";
    $ui->fh($fh);   # Default FH is STDERR.
    # We are not in an interactive mode, there should be at least one argument.
}


$ui->set_verbose($options{v}) if $options{v};

print "maintkeydb reversion: " . $REVISION ."\n" if $ui->get_verbose;

print "Uses: Net::DNS::SEC::Maint::KEY version ". $Net::DNS::SEC::Maint::Key::VERSION ."\n" if $ui->get_verbose;

# Net::DNS::SEC::Maint::Config is required by
# Net::DNS::SEC::Maint::Key hence available to us.

print "      Net::DNS::SEC::Maint::Config version ". $Net::DNS::SEC::Maint::Config::VERSION ."\n" if $ui->get_verbose;

print "Configuration read from:". $ui->{_keydb}->getconf("conffile") ."\n" if $ui->get_verbose;






if (my $term=$ui->get_term){
    my $t_attr=$term->Attribs;
    print "  shell interace mode.\n" if $ui->get_verbose;
    my @commands;
    # Populate the @commands array by looking at the symbol table.
    # if MAINTKEYDB::COMMAND::<BLA>::execute_command exists 
    # <BLA> is added to @commands
    # We'll use the array for command line completion.
    foreach my $command (keys %MAINTKEYDB::COMMAND::){
	if( defined *{ "MAINTKEYDB::COMMAND::".$command."execute_command" }){
	    $command=~s/::$//;
	    push @commands, lc $command;
	}
    }
    push @commands, "exit";
    push @commands, "quit";
    $t_attr->{completion_append_character} = '';
    $t_attr->{completion_entry_function} =
      $t_attr->{list_completion_function};

    my $cli_arg=join(" ",@ARGV);

READLOOP:    while (1){
	$t_attr->{completion_word} =\@commands;

	my $inline=$cli_arg || $term->readline("Command? >");
	$cli_arg="";
	last if not defined $inline;   # ctrl-d will exit the loop
	next READLOOP if $inline =~ /^\s*$/;
	last if $inline=~/^\s*(exit|quit)\s*$/;
	$term->add_history($inline);

	my $command=$ui->command($inline);

	next READLOOP unless ref($command)=~/^MAINTKEYDB::COMMAND/;
	if ($inline =~ s/^\s*\w+\s*help\b//){
	    # print_help exits in non_interactive shell.
	    # it will return when in interactive shell.
	    # 
	    $command->print_help;
	    next READLOOP;
	}

#       Hmmm.. for commands that only have optional arguments (like list)
#       match_full_cl will always return 0 (success). And you will not be
#       abble to enter the challenge-response loop.

#       So this is what we do... we make an exception

	if ($command->match_full_cl() || ($command->get_commandname eq "list" )){
	    my $err=$command->get_args_from_user;
	    $command->fh->print ($command->get_commandname .": ".$err."\n") if $err;
	    $command->execute_command() unless $err;
	}else{
	    $command->execute_command();
	}
    }


}else{
    print "  command line interace mode.\n" if $ui->get_verbose;

   my $command;
    if (! @ARGV){
	$command=$ui->command("HELP");
    }else{
	$command=$ui->command(join(" ",@ARGV));
    }


    $ui->exit_error("not_implemented") unless $command;
    $command->match_full_cl();
    $command->execute_command();
}





#####################################################################
#
#              END OF "MAIN"
#
#####################################################################





#
#############################################################################
#
#  PERLPOD documentation
#
#

=head1 NAME

maintkeydb - Command line interface to a database containing DNSSEC keys.

=head1 SYNOPSIS

        maintkeydb -h
	maintkeydb [-v verbose] [-c configfile] [-i] <commands> <arguments>

The -i switch turns on interactive mode, which is supposed to be more
verbose and guide you through the process (more or less current
functionality).

With the -h switch a short help is returned.

=head1 DESCRIPTION

the interface defines a number of commands:

	create
	delete_name
	delete_id
	list
	rollover
	showkeys
	parentdata
        state

In the interactive mode there are additional commands like "help"
and "exit".

The tool is designed with the procedures as documented in [1] in mind.

The basic commands take their own argument, see below.

=head1 COMMANDS


Note, in the argument descriptions <algorithm> is a shortcut for
<"RSASHA1"|"RSAMD5"|"RSA"|"DSA">

=head2 create


 maintkeydb create ["force"]  <"ksk"|"zsk"|"both"> <algorithm> <size> \
	<zone[[ zone],...]>

Create a KSK or ZSK file of given algorithm and size for the
specified zones.

If no ZSK exists for a given zone the create function will create
two keys, one marked as "active" (i.e. used for "signing"). This allows
for the rollover scheme as designed in [1].

If for the given zone and type a key marked as "active" exists the
create function will not mark newly created keys as "active".

If "default" is provided as the argument for size than the default sizes
from the dnssecmain.conf file are used.

To prevent mistakes that will cause the rollover commands to fail the
create method will not create a second KSK or a third ZSK unless the
"force" argument is specified.


Return values:

 returns 0 and prints the name of the created key to STDOUT 
 on success

 returns a nonzero vallue and potentionally prints warnings 
 to STDERR.


=head2 delete


has two forms:

 maintkeydb delete_id  <zone> <algorithm> <keyID[[ keyID] ...]> 

deletes one or more keys from the specified zone, the
keys are identified by the keyID.


 maintkeydb delete_name  <"ksk"|"zsk"|"both">  <algorithm> <zone[[ zone],...]> 

deletes all zsk or all ksk for the given zones. (Be careful with this command, it is
very destructive).


Return values:

 returns 0 and prints information  about the deleted keys to STDOUT on
 success

 returns a nonzero value and potentially prints warnings to STDERR on
 error


=head2 state

 maintkeydb state  <zone> <algorithm> <keyID[[ keyID] ...]> 

states the active/inactive state of the keys specified by the given
keyIDs. 

Usage of the state command may cause the database to be altered in 
such a way that the premises for the rollover commands are not met.


Return values:

 returns 0 on success

 returns a nonzero value and potentially prints warnings to STDERR on
 error



=head2 list

  maintkeydb list ["active"|"inactive"|"published"|"rolled"] 
  ["ksk"|"zsk"|"both"] [zone[[ zone],...]]

Lists keys in the database.  

Withouth any arguments all keys in the database are returned. 

 The output can be filtered to show only active, inactive, published
 or keys that are being rolled.
   
 when one or more zones are specified it only lists the keys for
 those zones.

 when "ksk" or "zsk" is specified only the ksk or zsk keys are
 listed.

 


Return values: 


returns 0 and prints a formated list to STDOUT if keys are found that
match the search criterea. The format of the output is a tab seperated
list of the following fields name, algorithm, keyid, KSK or ZSK,
active or inactive such as:.  

 example.com DSA 39372 ZSK active
 example.com RSASHA1 12672 ZSK inactive

returns 1 if no keys are found that match the search criterea

returns a value >1 if an error occurs and potentially prints warnings
to STDERR on error


=head2 rollover

  mainkeydb rollover <"ksk-stage1"|"ksk-stage1"|"zsk-stage1"|"zsk-stage2"> \
   <algorithm>  <zone[[ zone]...]>

rollovers the zsk or ksk for the specified zones.

The rollover only works in specific circumstances. The premises are
tested and if they are not sattisfied for _any_ of the specified zone
an error will be printed to STDERR and no rollover will be done for
any of the specified zones.

The premises under which rollover succeeds should be obeyed if the
user has never used the "create" command with the "force" argument and
has never issued "state" or "deleted" for the given zone.


Return values:

 returns 0 on success

 returns a nonzero value and potentially prints warnings to STDERR on
 error



=head3 ZSK rollover

This is an implementation of the "Pre-publish keyset rollover" as
documented in [1].

For the rollover of a ZSK the premises is that there is one active and
one inactive ZSK. During the rollover the inactive ZSK is made active,
the active ZSK is deleted and a new inactive ZSK is created.

=head3 KSK rollover

This is an implementation of the a double signature scheme keyrollover
as documented for KSK rollovers in [1].

The premises for the ksk-stage1 rollover is that there is are one or
more active KSK and only one published KSK. The rollover will mark the oldest
active KSK as being in "rollover" and make the published keys as active.

The premises of the ksk-stage2 rollover is that there are two or more
active KSKs one of wich, the oldest, is marked as being in
rollover. During the ksk-stage2 rollover the key marked as being in
rollover state will be removed and a new active KSK will be added.

The system does not check for the availability of DS RRs at the
parent.


=head3 Example.

This is what a user would do during a key rollover

Confirm that there is no key currently in rollover state. 
  bash> maintkeydb list rollover example.com
  (this command returns nothing if there are no keys being rolled)

Nor rollover start the rollover of the KSK key.
  bash> maintkeydb rollover ksk-stage1 example.com

List the key material and the DS record that needs to be send
to the parent.

  bash> maintkeydb showkeys ds example.com
  bash> 
  example.com IN KEY 256 3 5 ( AB12cD...
    ...
  example.com IN DS 1 ab123...  


After the zone is signed with the signing tool that uses the database.
And after the zone is published in the DNS the key shown will need to
be uploaded to the parent. The user will have to wait untill the DS RR
shown is served by the parents server, in this particular example the
com zone. Then the user can proceed with:

  bash> maintkeydb list rollover all
  example.com         	RSASHA1	27393	KSK active     (47d04h23m)	(R)

  bash> maintkeydb rollover ksk-stage2 example.com

Now resign and publish the zone in the DNS.


=head2 showkeys

   maintkeydb showkey ["ds"] [zone[[ zone],...]]

This command will show the DNSKEY RR set as it will appear in the zone.

If "ds" is specified than only the keysigning keys is printed
and in addition the DS RRs which match these.

Note that this command will print all the keysingin keys. If you want
to get the data that needs to be forwarded to the parent use the
parentdata command instead.

Note that the TTL of these records is set to 0.

Return values:

 returns 0 on success and prints the result to STDOUT

 returns a nonzero value and potentially prints warnings to STDERR on
 error



=head2 parentdata

   maintkeydb parentdata <"ds"|"key"|"both"> [zone[[ zone],...]]

Prints the data that will need to be sent to the parent for the
zones specified. 

The first argument specifies if the DNSKEY RRs, the DS RRs or both
need to be printed for each zone.

The output is sorted per zone.


=head1 LOGGING

Logging needs improvement, suggestions are welcomed.

Logging is provided through the LOG::Log4perl interface. The logging
configuration is read from /usr/local/etc/log4perl.conf,
/etc/log4perl.conf or set to the default

    log4perl.rootLogger=FATAL,Screen
    log4perl.appender.Logfile          = Log::Log4perl::Appender::File
    log4perl.appender.Logfile.filename = test.log
    log4perl.appender.Logfile.layout   = Log::Log4perl::Layout::PatternLayout
    log4perl.appender.Logfile.layout.ConversionPattern = [%r] %F %L %m%n

    log4perl.appender.Screen         = Log::Log4perl::Appender::Screen
    log4perl.appender.Screen.stderr  = 0
    log4perl.appender.Screen.layout = Log::Log4perl::Layout::SimpleLayout


Please consult LOG::Log4perl for assistance.



=head1 TODO, FEATURES, BUGS.

Zone names that have names that equal arguments in the funtion above
such as "ksk", "inactive" may cause parsing problems.

Add a "checkksk" command that will verify if the proper DS is
available for a KSK at the parent.

The error messages may be a little pedantic at times.


There is an error saying "You will need to hack the database". We will need
recovery procedures written.

=head1 REFERENCES

[1] Kolkman and Gieben, "DNSSEC Operational Practices",
   draft-ietf-dnsop-dnssec-operational-practices-01.txt (work in progress).



=head1 COPYRIGHT


=head1 COPYRIGHT

Copyright (c) 2004  RIPE NCC.  Author Olaf M. Kolkman <net-dns-sec@ripe.net>

All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of the author not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.


THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


=cut

1;
__END__


