#! /usr/bin/perl

use v5.10;
use warnings;
use strict;
use FindBin;
use File::Temp;
use Getopt::Long;
use Pod::Usage;
use File::Basename;
use if $ENV{DEBUG} => "Smart::Comments";
use DNStools::Config qw(get_config);

my $ME = basename $0;

sub read_conf(@);
sub read_argv($);
sub rm_keys($@);
sub ck_zone($@);
sub create_ksk($@);
sub create_zsk($@);
sub post_create($@);

MAIN: {
    ### reading config
    my %conf = get_config("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf");

    my ($cmd, @zones) = read_argv($conf{master_dir});

    given ($cmd) {
        when ("rm") { rm_keys($conf{master_dir}, @zones); exit }
        when ("ck") { ck_zone($conf{master_dir}, @zones) }
        when ("ksk") { create_ksk($conf{master_dir}, @zones) }
    };

    create_zsk($conf{master_dir}, @zones);
    post_create($conf{master_dir}, @zones);
}

sub read_argv ($) {
    my ($master_dir) = @_;
    my ($cmd, @zones);    # return

    GetOptions(
        "zsk"      => sub { $cmd = "zsk" },
        "ksk"      => sub { $cmd = "ksk" },
        "rm"       => sub { $cmd = "rm" },
        "ck|check" => sub { $cmd = "ck" },
        "h|help" => sub { pod2usage(-exitvalue => 0, -verbose => 1) },
        "m|man"  => sub {
            pod2usage(
                -exitvalue => 0,
                -noperldoc => system("perldoc -V &>/dev/null"),
                -verbose   => 2
            );
        },
      )
      and @ARGV
      or pod2usage;

    # checks the zones in argv if there are managed zones
    foreach (@ARGV) {
        chomp(my $zone = `idn --quiet "$_"`);

        die "zone $zone is not managed\n"
          if not -f "$master_dir/$zone/$zone";

        push @zones, $zone;
    }
    return ($cmd, @zones);
}


sub rm_keys ($@) {

    # deletes all the keys were handed over -rm in argv
    my ($master_dir, @zone) = @_;

    for (@zone) {
        my $zone = $_;

        my $zpf = "$master_dir/$zone";
        my $ep  = 0;

        if (-e "$zpf/$zone.signed") {
            unlink "$zpf/$zone.signed" and $ep = 1;
        }
        if (-e "$zpf/.keycounter") {
            unlink "$zpf/.keycounter" and $ep = 1;
        }
        if (-e "$zpf/.index.ksk") {
            unlink "$zpf/.index.ksk" and $ep = 1;
        }
        if (-e "$zpf/.index.zsk") {
            unlink "$zpf/.index.zsk" and $ep = 1;
        }
        if (-e "$zpf/dsset-$zone.") {
            unlink "$zpf/dsset-$zone." and $ep = 1;
        }
        if (-e "$zpf/keyset-$zone.") {
            unlink "$zpf/keyset-$zone." and $ep = 1;
        }

        for (glob("$zpf/K$zone*")) {
            chomp($_);
            unlink("$_");
        }

        if ($ep == 1) {
            print " * $zone: removed key-set\n";
        }

	open(my $old, "$zpf/$zone") or die "$zpf/$zone: $!\n";
	my $fh = File::Temp->new(DIR => $zpf) or die "Can't create tmpfile: $!\n";
	print $fh grep { not /^\s*\$INCLUDE.*"K$zone.*\.key"/i } <$old>;
	rename($fh->filename => "$zpf/$zone")
	    or die "Can't rename " . $fh->filename . " to $zpf/$zone: $!\n";
    }
}

sub create_ksk ($@) {
    my ($master_dir, @zone) = @_;
    my @index;
    my $keyname;

    for (@zone) {
        my $zone = $_;
        my $zpf  = "$master_dir/$zone";

        $keyname =
          `cd $zpf && dnssec-keygen -a RSASHA1 -b 2048 -f KSK -n ZONE $zone`;

        unless (-f "$zpf/.index.ksk") { @index = (); }
        else {
            open(INDEX, "$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n";
            @index = <INDEX>;
            close(INDEX);
        }

        push @index, $keyname;
        if (@index > 2) { shift(@index); }

        {
            my $fh = File::Temp->new(DIR => "$zpf")
              or die "Can't create tmpdir: $!\n";
            print $fh join "" => @index, "";
            rename($fh->filename => "$zpf/.index.ksk")
              or die "Can't rename "
              . $fh->filename
              . " to $zpf/.index.ksk: $!\n";
        }

        chomp($keyname);
        print " * $zone: new KSK $keyname\n";
        print "!! THE KSK must be published !! \n";

    }
}

sub create_zsk ($@) {
    my ($master_dir, @zone) = @_;
    my @index;
    my $keyname;

    for (@zone) {
        my $zone = $_;
        my $zpf  = "$master_dir/$zone";

        $keyname = `cd $zpf && dnssec-keygen -a RSASHA1 -b 512 -n ZONE $zone`;

        unless (-f "$zpf/.index.zsk") {
            @index = ();
        }
        else {
            open(INDEX, "$zpf/.index.zsk") or die "$zpf/.index.zsk: $!\n";
            @index = <INDEX>;
            close(INDEX);
        }

        push @index, $keyname;
        if (@index > 2) { shift(@index); }

        {
            my $fh = File::Temp->new(DIR => "$zpf")
              or die "Can't create tmpdir: $!\n";
            print $fh join "" => @index, "";
            rename($fh->filename => "$zpf/.index.zsk")
              or die "Can't rename "
              . $fh->filename
              . " to $zpf/.index.zsk: $!\n";
        }
        chomp($keyname);
        print " * $zone: new ZSK $keyname\n";

        open(KC, ">$zpf/.keycounter") or die "$zpf/keycounter: $!\n";
        print KC "0";
        close(KC);
    }
}

sub ck_zone ($@) {
    my ($master_dir, @zone) = @_;

    for (@zone) {
        my $zone = $_;
        my $zpf  = "$master_dir/$zone";
        my $keyfile;
        my @content;
        my @keylist;

        for (<$zpf/*>) {
            if (m#(K$zone.*\.key)#) {
                $keyfile = $1;
                open(KEYFILE, "<", "$zpf/$keyfile")
                  or die "$zpf/$keyfile: $!\n";
                @content = <KEYFILE>;
                close(KEYFILE);
                for (@content) {
                    if (m#DNSKEY.257#) {
                        push @keylist, $keyfile;
                    }
                }
            }
        }

        open(INDEX, ">$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n";
        for (@keylist) {
            s#\.key##;
            print INDEX "$_\n";
        }
        close(INDEX);

        print " * $zone: new .index.ksk created\n";
        if (-f "$zpf/.index.zsk") {
            unlink("$zpf/.index.zsk") or die "$zpf/.index.zsk: $!\n";
        }
    }
}

sub post_create ($@) {
    my ($master_dir, @zone) = @_;
    for (@zone) {
        my $zone = $_;
        `touch $master_dir/$zone/$zone`;
        &kill_useless_keys($zone, $master_dir);
        &key_to_zonefile($zone, $master_dir);
    }
}

sub kill_useless_keys ($@) {

    # the function deletes all keys that are not available in the zone

    my $zone       = $_[0];
    my $master_dir = $_[1];
    my @keylist    = ();
    my $zpf        = "$master_dir/$zone";

    open(INDEX, "<$zpf/.index.zsk") or die "$zpf/.index.zsk: $!\n";
    @keylist = <INDEX>;
    close(INDEX);
    open(INDEX, "<$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n";
    push @keylist, <INDEX>;

    # shortened the key name from the index file on the id in order to
    # be able to compare
    for (@keylist) {
        chomp;
        s#K.*\+.*\+(.*)#$1#;
    }

    # reviewed every key file (KSK, ZSK), whether they are described in
    # the respective index file. if not they will be deleted.
    for (glob("$master_dir/$zone/K*")) {
        chomp;
        my $file     = $_;
        my $rm_count = 1;
        my $keyname;
        for (@keylist) {
            if ($file =~ /$_/) { $rm_count = 0; }
        }
        if ($rm_count == 1) {
            unlink "$file";
            if ($file =~ /$zpf\/(.*\.key)/) {
                print " * $zone: Key $1 removed \n";
            }
        }
    }
}

sub key_to_zonefile ($@) {

    # the function added all keys to the indexfile
    my $zone       = $_[0];
    my $master_dir = $_[1];
    my $zpf        = "$master_dir/$zone";
    my @old_content;
    my @new_content = ();

    open(ZONEFILE, "<$zpf/$zone");
    @old_content = <ZONEFILE>;
    close(ZONEFILE);

    for (@old_content) {
        unless (m#INCLUDE.*key#) { push @new_content, $_; }
    }

    for (<$zpf/*>) {
        if (m#(.*\/)(K.*\.key)#) {
            push @new_content, "\$INCLUDE \"$2\"\n";
        }
    }
    open(ZONEFILE, ">$zpf/$zone") or die "$zpf/$zone: $!\n";
    print ZONEFILE @new_content;
    close(ZONEFILE);
}

__END__

=pod

=head1 NAME

dnssec-keytool

=head1 SYNOPSIS

dnssec-keytool {-z|-k|-r|-c} zone

=head1 DESCRIPTION

Blabla.

=head1 OPTIONS

=over

=item B<-z>  created a new ZSK

=item B<-k>  created a new ZSK and KSK

=item B<-r>  delete the key-set of a zone

=item B<-c>  created configuration files for the dnstools and a new ZSK for an existing KSK

=back

=cut

# vim:sts=4 sw=4 aw ai sm:
