merged
authorHeiko Schlittermann <hs@schlittermann.de>
Sun, 23 Jan 2011 00:30:15 +0100
changeset 88 0e1e5027e9c0
parent 52 53c95f2ff0ac (current diff)
parent 87 6d624831079f (diff)
child 90 0b9ba3e760bd
merged
Makefile
dnssec-keytool.pl
dnstools.conf
update-serial.pl
zone-ls.pl
zone-mk.pl
zone-rm.pl
--- a/.hgignore	Tue Dec 21 17:00:11 2010 +0100
+++ b/.hgignore	Sun Jan 23 00:30:15 2011 +0100
@@ -1,6 +1,6 @@
-.hgignore
-dnssec-keytool
-update-serial
-zone-ls
-zone-mk
-zone-rm
+bind
+blib
+Build
+_build
+cover_db
+pod2htm.\.tmp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,1 @@
+9c304b77cf02c77538ad7fc983f30f04fd50ebdd 0.1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Build.PL	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,22 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use Module::Build;
+
+my $build = Module::Build->new(
+    module_name    => "dnstools",
+    dist_author    => "schlittermann.de",
+    dist_version   => "0.1",
+    create_license => 1,
+    license        => "gpl",
+    requires       => {
+        perl          => "5.10.0",
+        "Net::LibIDN" => "0",
+        "Template"    => "0"
+    },
+    build_requires => { "Pod::Coverage" => 0, "Test::Command" => "0.08" },
+    script_files => [glob "bin/*"],    # avoid .swp files
+);
+
+$build->create_build_script;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MANIFEST.SKIP	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,50 @@
+
+#!start included /usr/share/perl/5.10/ExtUtils/MANIFEST.SKIP
+# Avoid version control files.
+^\.hg/
+^\.hg[^_]
+.*\.sw?$
+\bRCS\b
+\bCVS\b
+\bSCCS\b
+,v$
+\B\.svn\b
+\B\.git\b
+\B\.gitignore\b
+\b_darcs\b
+
+# Avoid Makemaker generated and utility files.
+\bMANIFEST\.bak
+\bMakefile$
+\bblib/
+\bMakeMaker-\d
+\bpm_to_blib\.ts$
+\bpm_to_blib$
+\bblibdirs\.ts$         # 6.18 through 6.25 generated this
+
+# Avoid Module::Build generated and utility files.
+\bBuild$
+\b_build/
+
+# Avoid temp and backup files.
+~$
+\.old$
+\#$
+\b\.#
+\.bak$
+
+# Avoid Devel::Cover files.
+\bcover_db\b
+#!end included /usr/share/perl/5.10/ExtUtils/MANIFEST.SKIP
+
+
+# Avoid Module::Build generated and utility files.
+\bBuild$
+\bBuild.bat$
+\b_build
+\bBuild.COM$
+\bBUILD.COM$
+\bbuild.com$
+
+# Avoid archives of this distribution
+\bdnstools-[\d\.\_]+
--- a/Makefile	Tue Dec 21 17:00:11 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-ALL = zone-ls zone-mk zone-rm \
-      update-serial \
-      dnssec-keytool
-
-CLEANFILES = $(ALL)
-
-.PHONY:	all clean distclean install
-
-all:	$(ALL)
-
-clean:	
-distclean:
-	rm -f $(CLEANFILES)
-
-
-%:	%.pl
-	@perl -c $<
-	@cp -f $< $@
-	@chmod a+x-w $@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/.perltidyrc	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,1 @@
+../.perltidyrc
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/dnssec-keytool	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,349 @@
+#! /usr/bin/perl
+
+use v5.10;
+use warnings;
+use strict;
+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();
+
+    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:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/update-serial	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,548 @@
+#!/usr/bin/perl 
+
+use v5.10;
+use strict;
+use warnings;
+
+use File::Basename;
+use Pod::Usage;
+use Getopt::Long;
+use File::Temp;
+use IO::File;
+use POSIX qw(strftime);
+use if $ENV{DEBUG} => "Smart::Comments";
+use DNStools::Config qw(get_config);
+
+sub uniq(@);
+sub zones(@);
+sub changed_zones();
+sub update_index($);
+sub signature_expired($);
+sub need_rollover();
+sub done_rollover();
+sub begin_rollover(@);
+sub end_rollover(@);
+sub unlink_unused_keys($);
+sub include_keys($);
+sub sign($);
+sub update_serial($);
+
+sub mk_zone_conf;
+sub file_entry;
+sub server_reload;
+
+my %config;
+my %opt;
+
+MAIN: {
+
+    GetOptions(
+        "sign-alert-time=i" => \$opt{sign_alert_time},
+        "key-counter-end=i" => \$opt{key_counter_end},
+        "h|help"            => sub { pod2usage(-exit 0, -verbose => 1) },
+        "m|man"             => sub {
+            pod2usage(
+                -exit 0,
+                -verbose   => 2,
+                -noperldoc => system("perldoc -v &>/dev/null")
+            );
+        },
+    ) or pod2usage;
+
+    # merge the config and the defined options from commandline
+    %config = get_config("$ENV{DNSTOOLS_CONF}", "dnstools.conf",
+        "$ENV{HOME}/.dnstools.conf", "/etc/dnstools.conf", \%opt);
+
+    our $bind_dir = $config{bind_dir};
+    our $conf_dir = $config{zone_conf_dir};
+
+    my @candidates = @ARGV ? zones(@ARGV) : changed_zones;
+    push @candidates, update_index($config{indexzone});
+    push @candidates, signature_expired($config{sign_alert_time});
+
+    my @need_rollover = need_rollover;
+    my @done_rollover = done_rollover;
+
+    push @candidates, begin_rollover(@need_rollover);
+    push @candidates, end_rollover(@done_rollover);
+
+    foreach my $zone (uniq(@candidates)) {
+        update_serial($zone);
+        sign($zone);
+    }
+    say "Need to ... file_entry, mk_zone_conf, server_reload";
+    exit;
+
+    file_entry;       # bearbeitet die file-eintraege der konfigurations-datei
+    mk_zone_conf;     # konfiguration zusammenfuegen
+    server_reload;    # server neu laden
+
+}
+
+sub uniq(@) {
+
+    # remove duplicate entries
+    my %all;
+    @all{@_} = ();
+    keys %all;
+}
+
+sub zones(@) {
+
+    # check whether the zones in argv are managed zones and
+    # insert them into the list new_serial
+
+    my @r;
+
+    foreach (@_) {
+        chomp(my $zone = `idn --quiet "$_"`);
+        die "$zone is not managed\n"
+          if not -e "$config{master_dir}/$zone/$zone";
+        push @r, $zone;
+    }
+
+    return @r;
+}
+
+sub changed_zones() {
+
+    # find candidates in our master dir
+    my @r;
+
+    while (glob "$config{master_dir}/*") {
+        my $zone = basename($_);
+
+        if (not -e "$_/.stamp") {
+            say " * $zone: no .stamp file found";    # NOCH IN NEW_SERIAL PUSHEN
+            push @r, $zone;
+            next;
+        }
+
+        my $stamp_age = -M _;
+        my $file_age  = -M "$_/$zone";
+
+        next if $stamp_age <= $file_age;             # should be only <
+
+        push @r, $zone;
+        say " * $zone: zone file modified";
+    }
+    return @r;
+}
+
+sub signature_expired($) {
+    my $sign_alert_time = shift;  # the time between the end and the new signing
+                                  # (see external configuration)
+    my @r;
+
+# erzeugt $time (die zeit ab der neu signiert werden soll)
+# ... warum eigentlich nur bis zu den Stunden und nicht auch Minuten und Sekunden?
+    my $time = strftime("%Y%m%d%H" => localtime time + 3600 * $sign_alert_time);
+
+    ## vergleicht fuer alle zonen im ordner $config{master_dir} mit einer
+    ## <zone>.signed-datei den zeitpunkt in $time mit dem ablaufdatum der
+    ## signatur, welcher aus der datei <zone>.signed ausgelesen wird.
+  ZONE: while (my $dir = glob "$config{master_dir}/*") {
+        my $zone = basename $dir;
+
+        next if not -e "$dir/$zone.signed";
+
+        open(my $fh, "$dir/$zone.signed")
+          or die "Can't open $dir/$zone.signed: $!\n";
+        push @r, $zone
+          if /RRSIG\s+SOA[\d ]+(\d{10})\d{4}\s+\(/ ~~ [<$fh>]
+              and $1 < $time;
+    }
+
+    return @r;
+}
+
+sub sign($) {
+
+    my $zone = shift;
+    my $dir  = "$config{master_dir}/$zone";
+
+    my $pid = fork // die "Can't fork: $!";
+
+    if ($pid == 0) {
+        chdir $dir or die "Can't chdir to $dir: $!\n";
+        exec "dnssec-signzone" => $zone;
+        die "Can't exec: $!\n";
+    }
+
+    wait == $pid or die "Child is lost: $!";
+    die "Can't sign zone!" if $?;
+
+    say " * $zone neu signiert";
+
+    open(my $fh, "+>>$dir/.keycounter")
+      or die "Can't open $dir/.keycounter for update: $!\n";
+    seek($fh, 0, 0);
+    my $kc = <$fh>;
+    truncate($fh, 0);
+    say $fh ++$kc;
+}
+
+sub update_serial($) {
+
+    my $zone = shift;
+
+    my $file = "$config{master_dir}/$zone/$zone";
+    my $in   = IO::File->new($file) or die "Can't open $file: $!\n";
+    my $out  = File::Temp->new(DIR => dirname $file)
+      or die "Can't open tmpfile: $!\n";
+    my $_ = join "" => <$in>;
+
+    my $serial;
+    s/^(\s+)(\d{10})(?=\s*;\s*serial)/$1 . ($serial = new_serial($2))/emi
+      or die "Serial number not found for replacement!";
+
+    print $out $_;
+
+    close($in);
+    close($out);
+
+    rename($out->filename => $file)
+      or die "Can't rename tmp to $file: $!\n";
+
+    $serial =~ s/\s*//g;
+    say " * $zone: serial incremented to $serial";
+
+    open(my $stamp, ">", dirname($file) . "/.stamp");
+    print $stamp time() . " " . localtime() . "\n";
+
+    say " * $zone: stamp aktualisiert";
+}
+
+sub new_serial($) {
+
+    my ($date, $cnt) = $_[0] =~ /(\d{8})(\d\d)/;
+
+    state $now = strftime("%4Y%02m%02d", localtime);
+
+    return $date eq $now
+      ? sprintf "%s%02d", $date, $cnt + 1
+      : "${now}00";
+
+}
+
+sub mk_zone_conf {
+
+    # erzeugt eine named.conf-datei aus den entsprechenden vorlagen.
+    our $bind_dir;
+    our $conf_dir;
+
+    open(TO, ">$bind_dir/named.conf.zones")
+      or die "$bind_dir/named.conf.zones: $!\n";
+    while (<$conf_dir/*>) {
+        open(FROM, "$_") or die "$_: $! \n";
+        print TO <FROM>;
+        close(FROM);
+    }
+    close(TO);
+    print "** zonekonfiguration erzeugt\n";
+}
+
+sub update_index($) {
+    my $indexzone = shift;
+
+    my @iz;
+
+    {
+        open(my $fh, "$config{master_dir}/$indexzone/$indexzone")
+          or die "$config{master_dir}/$indexzone/$indexzone: $!\n";
+        chomp(@iz = grep !/ZONE::/ => <$fh>);
+    }
+
+    for my $dir (glob "$config{master_dir}/*") {
+        my $zone = basename($dir);
+        my $info = -e ("$dir/.keycounter") ? "sec-on" : "sec-off";
+        push @iz, join "::", "\t\tIN TXT\t\t\"ZONE", $zone, $info;
+    }
+
+    {
+        my $fh = File::Temp->new(DIR => "$config{master_dir}/$indexzone")
+          or die "Can't create tmpdir: $!\n";
+        print $fh join "\n" => @iz, "";
+        rename($fh->filename => "$config{master_dir}/$indexzone/$indexzone")
+          or die "Can't rename "
+          . $fh->filename
+          . " to $config{master_dir}/$indexzone/$indexzone: $!\n";
+    }
+
+    say "** index-zone aktualisiert";
+    return $indexzone;
+}
+
+sub file_entry {
+
+    # prueft jede domain, die ein verzeichnis in $config{master_dir} hat, ob sie
+    # dnssec nutzt.
+    # passt die eintraege in $config_file falls noetig an.
+    our $conf_dir;
+
+    while (glob "$config{master_dir}/*") {
+        s#($config{master_dir}/)(.*)#$2#;
+        my $zone      = $_;
+        my $zone_file = "$config{master_dir}/$zone/$zone";
+        my $conf_file = "$conf_dir/$zone";
+        my @c_content;
+
+        unless (-f "$conf_file") {
+            die "$conf_file: $! \n";
+        }
+
+        if (-e "$config{master_dir}/$zone/.keycounter") {
+            open(FILE, "<$conf_file") or die "$conf_file: $!\n";
+            @c_content = <FILE>;
+            close(FILE);
+            for (@c_content) {
+                if (m{(.*)($zone_file)(";)}) {
+                    print
+                      " * zonekonfiguration aktualisiert ($2 ==> $2.signed)\n";
+                    $_ = "$1$2.signed$3\n";
+                }
+            }
+            open(FILE, ">$conf_file") or die "$conf_file: $!\n";
+            print FILE @c_content;
+            close(FILE);
+        }
+        else {
+            open(FILE, "<$conf_file") or die "$conf_file: $!\n";
+            @c_content = <FILE>;
+            close(FILE);
+            for (@c_content) {
+                if (m{(.*)($zone_file)\.signed(.*)}) {
+                    print
+                      " * zonekonfiguration aktualisiert ($2.signed ==> $2)\n";
+                    $_ = "$1$2$3\n";
+                }
+            }
+            open(FILE, ">$conf_file") or die "$conf_file: $!\n";
+            print FILE @c_content;
+            close(FILE);
+        }
+    }
+}
+
+sub server_reload {
+    if (`rndc reload`) { print "** reload dns-server \n" }
+}
+
+sub need_rollover() {
+
+    # gibt alle zonen mit abgelaufenen keycounter
+    my @r;
+
+    while (my $kc = glob "$config{master_dir}/*/.keycounter") {
+        my $zone = basename dirname $kc;
+        my $key;
+
+        {
+            open(my $fh, $kc) or die "$kc: $!\n";
+            chomp($key = <$fh>);
+        }
+
+        push @r, $zone if $config{key_counter_end} <= $key;
+    }
+
+    return @r;
+}
+
+sub done_rollover() {
+
+    # funktion ueberprueft ob ein keyrollover fertig ist
+    # die bedingung dafuer ist das:
+    # - eine datei .index.zsk vorhanden ist
+    # - die datei .index.zsk älter ist, als die rollover-Zeit
+    # - die datei .index.zsk ueber mehr als eine zeile gross ist
+    #   (also mehr als einen Schlüssel enthält)
+    my @r;
+    my $now = time;
+
+    while (my $dir = glob "$config{master_dir}/*") {
+        my $zone = basename $dir;
+
+        my @index = ();
+        my $index_wc;
+
+        # prueft nach der ".index.zsk"-datei und erstellt den zeitpunkt
+        # an dem das key-rollover endet.
+        # rollover is done when mtime of the .index.zsk + abl_zeit is
+        # in the past
+        next if not -e "$dir/.index.zsk";
+        next if (stat _)[9] + 3600 * $config{abl_zeit} >= $now;
+
+        # prueft die anzahl der schluessel in der .index.zsk
+        open(my $fh, "$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
+        (<$fh>);
+        push @r, $zone if $. > 1;
+    }
+
+    return @r;
+}
+
+sub begin_rollover(@) {
+    my @zones = @_;
+    my @r;
+
+    # anfang des key-rollovers
+
+    foreach my $zone (@zones) {
+
+        # erzeugt zsks
+        my $dir = "$config{master_dir}/$zone";
+        my ($keyname, @keys);
+
+        # create a new key
+        {    # need to change the direcoty, thus some more effort
+                # alternativly: $keyname = `cd $dir && dnssec-keygen ...`;
+                # would do, but is more fragile on shell meta characters
+
+            open(my $keygen, "-|") or do {
+                chdir $dir or die "Can't chdir to $dir: $!\n";
+                exec "dnssec-keygen",
+                  -a => "RSASHA1",
+                  -b => 512,
+                  -n => "ZONE",
+                  $zone;
+                die "Can't exec: $!";
+            };
+            chomp($keyname = <$keygen>);
+            close($keygen) or die "dnssec-keygen failed: $@";
+        }
+
+        open(my $fh, "+>>$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
+        seek($fh, 0, 0);
+        chomp(@keys = <$fh>);
+
+        ### @keys
+
+        push @keys, $keyname;
+        shift @keys if @keys > 2;
+
+        truncate($fh, 0) or die "truncate";
+        print $fh join "\n" => @keys;
+
+        print " * $zone: neuer ZSK $keyname erstellt\n";
+
+        open($fh, ">$dir/.keycounter") or die "$dir/.keycounter: $!\n";
+        say $fh 0;
+        close($fh);
+
+        unlink_unused_keys($zone);
+        include_keys($zone);
+        push @r, $zone;
+    }
+
+    return @r;
+}
+
+sub include_keys($) {
+
+    # die funktion fugt alle schluessel in eine zonedatei
+    my $zone = shift;
+    my $dir  = "$config{master_dir}/$zone";
+
+    my $in = IO::File->new("$dir/$zone") or die "Can't open $dir/$zone: $!\n";
+    my $out = File::Temp->new(DIR => $dir) or die "Can't open tmpfile: $!\n";
+
+    print $out grep { !/\$include\s+.*key/i } $in;
+    print $out map  { "\$INCLUDE @{[basename $_]}\n" } glob "$dir/K*key";
+
+    close $in;
+    close $out;
+    rename($out->filename => "$dir/$zone")
+      or die "Can't rename tmp to $dir/$zone: $!\n";
+
+}
+
+sub unlink_unused_keys($) {
+
+    # die funktion loescht alle schluessel die nicht in der index.zsk
+    # der uebergebenen zone stehen
+    my $zone = shift;
+
+    my @keys;
+    my $dir = "$config{master_dir}/$zone";
+
+    {
+
+        # collect the keys and cut everything except the key id
+        # we cut the basenames (w/o the .private|.key suffix)
+        open(my $zsk, "<$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
+        open(my $ksk, "<$dir/.index.ksk") or die "$dir/.index.ksk: $!\n";
+        @keys = (<$zsk>, <$ksk>);
+    }
+
+    # prueft alle schluesseldateien (ksk, zsk) ob sie in der jeweiligen
+    # indexdatei beschrieben sind. wenn nicht werden sie geloescht.
+    for my $file (glob "$dir/K*.key $dir/K*.private") {
+        unlink $file if basename($file, ".key", ".private") ~~ @keys;
+    }
+}
+
+sub end_rollover(@) {
+
+    my @zones = @_;
+    my @r;
+
+    foreach my $zone (@zones) {
+
+        my $dir = "$config{master_dir}/$zone";
+
+        open(my $fh, "+>>$dir/.index.zsk")
+          or die "Can't open $dir/.index.zsk: $!\n";
+        seek($fh, 0, 0);
+        chomp(my @keys = <$fh>);
+
+        if (@keys > 1) {
+            truncate($fh, 0);
+            say $fh $keys[-1];
+        }
+        close($fh);
+
+        unlink_unused_keys($zone);
+        include_keys($zone);
+        push @r => $zone;
+    }
+
+    return @r;
+}
+
+__END__
+
+=head1 NAME
+ 
+ update-serial - updates the serial numbers and re-signs the zone files
+
+=head1 SYNOPSIS
+
+ update-serial [options] [zone...]
+
+=head1 DESCRIPTION
+
+B<update-serial> scans the configured directories for modified zone files. On any
+file found it increments the serial number and signs the zone, if approbiate.
+
+=head1 OPTIONS
+
+=over
+
+=item B<--sign-alert-time> I<days>
+
+=item B<--key-counter-end> I<integer>
+
+Maximum number if key usages.
+
+
+=back
+
+The common options B<-h>|B<--help>|B<-m>|B<--man> are supported.
+
+=head1 AUTHOR
+
+L<andre.suess@pipkin.cc>
+
+=cut
+
+# vim:sts=4 sw=4 aw ai sm:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/zone-ls	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,144 @@
+#! /usr/bin/perl
+
+use v5.10;
+use strict;
+use warnings;
+use Pod::Usage;
+use File::Basename;
+use Time::Local;
+use Getopt::Long;
+use if $ENV{DEBUG} => "Smart::Comments";
+use DNStools::Config qw(get_config);
+
+my %config;
+my $opt_expiry = undef;
+
+MAIN: {
+    my %info;    # will hold the information we collected
+
+    GetOptions(
+        "e|expiry" => \$opt_expiry,
+        "h|help"   => sub { pod2usage(-exit => 0, -verbose => 1) },
+        "m|man"    => sub {
+            pod2usage(
+                -exit      => 0,
+                -verbose   => 2,
+                -noperldoc => system("perldoc -V &>/dev/null")
+            );
+        },
+    ) or pod2usage;
+
+    %config = get_config();
+    die "$config{master_dir}: $!\n" if not -d $config{master_dir};
+
+    foreach my $dir (grep { -d } glob "$config{master_dir}/*") {
+
+        my $zone = basename($dir);
+        $info{$zone} = { status => "OK" };
+
+        if (not -f "$dir/.index.zsk") {
+            $info{$zone}{zsk}    = 0;
+            $info{$zone}{ksk}    = 0;
+            $info{$zone}{kc}     = 0;
+            $info{$zone}{end}    = "-";
+            $info{$zone}{expiry} = undef;
+            next;
+        }
+
+        # prueft wie viele zsks genutzt werden
+        {
+            open(my ($fh), $_ = "<$dir/.index.zsk")
+              or die "Can't open $_: $!\n";
+            () = <$fh>;
+            $info{$zone}{zsk} = $.
+        }
+
+        # prueft wie viele ksks genutzt werden
+        {
+            open(my ($fh), $_ = "<$dir/.index.ksk")
+              or die "Can't open $_: $!\n";
+            () = <$fh>;
+            $info{$zone}{ksk} = $.
+        }
+
+        # prueft wie oft die schluessel zum signieren genutzt wurden
+        {
+            open(my ($fh), $_ = "<$dir/.keycounter")
+              or die "Can't open $_: $!\n";
+            chomp($info{$zone}{kc} = <$fh>);
+        }
+
+        # prueft das ablaufdatum
+        if (!-f "$dir/$zone.signed") {
+            $info{$zone}{end} = "-";
+            next;
+        }
+
+        open(my ($fh), $_ = "<$dir/$zone.signed") or die "Can't open $_: $!\n";
+        while (<$fh>) {
+            next if not /RSIG\s+SOA\s.*\s
+				(?<year>\d\d\d\d)
+				(?<mon>\d\d)
+				(?<day>\d\d)
+				(?<hour>\d\d)
+				(?<min>\d\d)\d+\s\(/ix;
+            $info{$zone}{end} = "$+{day}.$+{mon}.$+{year} $+{hour}:$+{min}";
+            $info{$zone}{expiry} =
+              timelocal(0, $+{min}, $+{hour}, $+{day}, $+{mon} - 1, $+{year});
+        }
+    }
+
+    {    # output
+
+        my $sort_by =
+          $opt_expiry
+          ? sub { ($info{$a}{expiry} // 2**64) <=> ($info{$b}{expiry} // 2**64) }
+          : sub { $a cmp $b };
+
+        my $format_h = "%-35s %-8s %1s/%1s %3s %7s\n";
+        my $format_l = "%-35s %-8s %1d/%1d %5d %19s\n";
+
+        printf $format_h => qw(Domain Status ZSK KSK Used Sig-end);
+
+        foreach my $zone (sort $sort_by keys %info) {
+            printf $format_l => $zone,
+              @{ $info{$zone} }{qw(status zsk ksk kc end)};
+        }
+    }
+}
+
+__END__
+
+=head1 NAME
+
+ zone-ls -- lists all zones
+
+=head1 SYNOPSIS
+
+ zone-ls [-e|--expiry]
+
+=head1 DESCRIPTION
+
+This B<zone-ls> lists all zones under control of our dnstools suite. The output is ordered by domain name.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-e>|B<--expiry>
+
+Order the output by expiry date. The sooner the key expires, the more top the
+domain is listed.
+
+=back
+
+Additionally the common B<-h>|B<--help>|B<-m>|B<--man> options, which should be
+self explanatory.
+
+=head1 AUTHORS
+
+L<andre.suess@pipkin.cc>
+
+=cut
+
+# vim:ts=4 sw=4 ai si aw:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/zone-mk	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,120 @@
+#!/usr/bin/perl 
+
+use 5.010;
+use warnings;
+use strict;
+use Pod::Usage;
+use if $ENV{DEBUG} => "Smart::Comments";
+use Cwd qw(abs_path);
+use File::Path qw(make_path);
+use File::Basename;
+use Getopt::Long;
+use Net::LibIDN qw(:all);
+use DNStools::Config qw(get_config);
+use Template;
+
+my $CHARSET = "UTF-8";
+
+my $opt_force = 0;
+
+MAIN: {
+
+    my %cf = get_config;
+
+    GetOptions(
+        "f|force" => \$opt_force,
+        "h|help"  => sub { pod2usage(-verbose => 1, -exit => 0) },
+        "m|man"   => sub {
+            pod2usage(
+                -verbose   => 2,
+                -noperldoc => system("perldoc -V &>/dev/null"),
+                -exit      => 0
+            );
+        },
+      )
+      and @ARGV >= 2
+      or pod2usage;
+
+    my $customer = shift;
+
+    die "$cf{master_dir}: $!"    if not -d -r -x $cf{master_dir};
+    die "$cf{zone_conf_dir}: $!" if not -d -r -x $cf{zone_conf_dir};
+
+    # legt fuer jede domain in @ARGV ein verzeichnis in $master_dir an.
+    # schreibt aus den angegebenen templates die dateien $zonefile und $config
+    # in die entsprechenden verzeichnisse.
+    for my $utf8domain (@ARGV) {
+
+        my $domain     = idn_to_ascii($utf8domain, $CHARSET);
+        my $zonefile   = "$cf{master_dir}/$domain/$domain";
+        my $configfile = "$cf{zone_conf_dir}/$domain";
+        my $now        = time;
+
+        make_path dirname $zonefile;
+
+        if (-f $zonefile and not $opt_force) {
+            say "skipping $utf8domain: zone file '$zonefile' exists.";
+            next;
+        }
+
+        if (-f $configfile and not $opt_force) {
+            say "skipping $utf8domain: config file '$configfile' exists.";
+            next;
+        }
+
+        say "domain $utf8domain ($domain) for $customer.";
+
+        my %vars = (
+            domain     => $domain,
+            utf8domain => $utf8domain,
+            now        => $now,
+            zonefile   => abs_path($zonefile),
+            customer   => $customer,
+            hostmaster => $cf{hostmaster},
+            primary    => $cf{primary},
+            secondary  => $cf{secondary},
+        );
+
+        my $tt = Template->new(INCLUDE_PATH => $cf{template_dir})
+          or die "$Template::ERROR\n";
+
+        $tt->process("named.zone",   \%vars, $zonefile)   || die $tt->error;
+        $tt->process("named.config", \%vars, $configfile) || die $tt->error;
+
+    }
+
+}
+
+__END__
+
+=head1 NAME
+
+    zone-mk - create a new zone
+
+=head1 SYNOPSIS
+
+    zone-mk [-f|--force] <customer-id> <domain>...
+
+=head1 DESCRIPTION
+
+B<zone-mk> creates a new DNS zone file and the config snipped.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-f>|B<--force>
+
+Crate zone file and config even if they exist. (default: off)
+
+=item B<-h>|B<--help>
+
+=item B<-m>|B<--man>
+
+=back
+
+=head1 FILES
+
+The F<dnstools.conf> is used.
+
+=cut
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/zone-rm	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,32 @@
+#!/usr/bin/perl 
+
+use warnings;
+use strict;
+use File::Path;
+use DNStools::Config qw(get_config);
+
+# liest die Konfiguration ein
+my %config = get_config();
+
+my $master_dir = $config{"master_dir"};
+my $conf_dir   = $config{"zone_conf_dir"};
+
+for (@ARGV) {
+    chomp(my $zone = `idn --quiet "$_"`);
+
+    if (-d "$master_dir/$zone") {
+        rmtree "$master_dir/$zone/"
+          and print "zone-dir for $zone removed\n";
+    }
+    else {
+        print "$master_dir/$zone: $!\n";
+    }
+
+    if (-e "$conf_dir/$zone") {
+        unlink "$conf_dir/$zone"
+          and print "configuration-file for $zone removed\n";
+    }
+    else {
+        print "$conf_dir/$zone: $!\n";
+    }
+}
--- a/dnssec-keytool.pl	Tue Dec 21 17:00:11 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,370 +0,0 @@
-#! /usr/bin/perl
-
-use warnings;
-use strict;
-use FindBin;
-
-sub del_double {
-    my %all;
-    grep { $all{$_} = 0 } @_;
-    return (keys %all);
-}
-
-sub read_conf {
-
-    # read configuration
-    my @configs = ("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf");
-    our %config;
-
-    for (grep { -f } @configs) {
-        open(CONFIG, $_) or die "Can't open $_: $!\n";
-    }
-    unless (seek(CONFIG, 0, 0)) {
-        die "Can't open config (searched: @configs)\n";
-    }
-    while (<CONFIG>) {
-        chomp;
-        s/#.*//;
-        s/\t//g;
-        s/\s//g;
-
-        next unless length;
-        my ($cname, $ccont) = split(/\s*=\s*/, $_, 2);
-        $config{$cname} = $ccont;
-    }
-    close(CONFIG);
-}
-
-sub read_argv {
-    # evaluate argv or print the help
-    my $arg = shift @ARGV;
-    my $zone;
-    our $do;
-    our @zones;
-    our $master_dir;
-
-    if (!defined $arg) {
-        print " usage: dnssec-keytool <option> zone\n";
-        print "   -z  created a new ZSK\n";
-        print "   -k  created a new ZSK and KSK\n";
-        print "   -rm deletes the key-set of a zone\n";
-        print "   -c  created configuration files for the dnstools\n";
-        print "       and a new ZSK for an existing KSK\n";
-        print "\n";
-
-        exit;
-    }
-    elsif ($arg eq "-k")  { $do = "ksk"; }
-    elsif ($arg eq "-rm") { $do = "rm"; }
-    elsif ($arg eq "-c")  { $do = "ck"; }
-    elsif ($arg eq "-z")  { $do = "zsk"; }
-    else {
-        print "not a valid option.\n";
-        exit;
-    }
-
-    # checks the zones in argv if there are managed zones
-    for (@ARGV) {
-        chomp($zone = `idn --quiet "$_"`);
-        if (-e "$master_dir/$zone/$zone") {
-            push @zones, $zone;
-        }
-    }
-}
-
-sub rm_keys {
-    # deletes all the keys were handed over -rm in argv
-    our @zones;
-    our $master_dir;
-    my $zone;
-    my @new_zone_content;
-    my @old_zone_content;
-
-    for (@zones) {
-        $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(ZONE, "$zpf/$zone")
-          or die "$zpf/$zone: $!\n";
-        @old_zone_content = <ZONE>;
-        close(ZONE);
-
-        for (@old_zone_content) {
-            unless (m#\$INCLUDE.*\"K$zone.*\.key\"#) {
-                push @new_zone_content, $_;
-            }
-        }
-
-        open(ZONE, ">$zpf/$zone") or die "$zpf/$zone: $!\n";
-        print ZONE @new_zone_content;
-        close(ZONE);
-    }
-}
-
-sub creat_ksk {
-    our @zones;
-    our $master_dir;
-    my @index;
-    my $zone;
-    my $keyname;
-    my $zpf;
-
-    for (@zones) {
-        $zone = $_;
-        $zpf  = "$master_dir/$zone";
-
-        chdir "$zpf" or die "$zpf: $!\n";
-        $keyname = `dnssec-keygen -a RSASHA1 -b 2048 -f KSK -n ZONE $zone`;
-
-        unless (-f ".index.ksk") { @index = (); }
-        else {
-            open(INDEX, ".index.ksk") or die "$zpf/.index.ksk: $!\n";
-            @index = <INDEX>;
-            close(INDEX);
-        }
-
-        push @index, $keyname;
-        if (@index > 2) { shift(@index); }
-
-        open(INDEX, ">.index.ksk") or die "$zpf/.index.ksk: $!\n";
-        print INDEX @index;
-        close(INDEX);
-
-        chomp($keyname);
-        print " * $zone: new KSK $keyname\n";
-
-        print "!! THE KSK must be published !! \n";
-
-    }
-}
-
-sub creat_zsk {
-    our @zones;
-    our $master_dir;
-    my @index;
-    my $zone;
-    my $keyname;
-    my $zpf;
-
-    for (@zones) {
-        $zone = $_;
-        $zpf  = "$master_dir/$zone";
-
-        chdir "$zpf" or die "$zpf: $!\n";
-        $keyname = `dnssec-keygen -a RSASHA1 -b 512 -n ZONE $zone`;
-
-        unless (-f ".index.zsk") { @index = (); }
-        else {
-            open(INDEX, ".index.zsk") or die "$zpf/.index.zsk: $!\n";
-            @index = <INDEX>;
-            close(INDEX);
-        }
-
-        push @index, $keyname;
-        if (@index > 2) { shift(@index); }
-
-        open(INDEX, ">.index.zsk") or die "$zpf/.index.zsk: $!\n";
-        print INDEX @index;
-        close(INDEX);
-
-        chomp($keyname);
-        print " * $zone: new ZSK $keyname\n";
-
-        open(KC, ">.keycounter") or die "$zpf/keycounter: $!\n";
-        print KC "0";
-        close(KC);
-
-    }
-}
-
-sub ck_zone {
-    our @zones;
-    our $master_dir;
-    my $zone;
-
-    for (@zones) {
-        $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");
-                @content = <KEYFILE>;
-                close(KEYFILE);
-                for (@content) {
-                    if (m#DNSKEY.257#) {
-                        push @keylist, $keyfile;
-                    }
-                }
-            }
-        }
-
-        open(INDEX, ">.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_creat {
-    our @zones;
-    our $master_dir;
-
-    for (@zones) {
-        my $zone = $_;
-        `touch $master_dir/$zone/$zone`;
-
-        &kill_useless_keys($zone);
-        &key_to_zonefile($zone);
-    }
-
-}
-
-sub kill_useless_keys {
-
-    # the function deletes all keys that are not available in the zone
-    # of index.zsk
-    our $master_dir;
-    my $zone    = $_[0];
-    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: Schluessel $1 entfernt \n";
-            }
-        }
-    }
-}
-
-sub key_to_zonefile {
-
-    # the function added all keys to the indexfile
-    our $master_dir;
-    my $zone = $_[0];
-    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);
-}
-
-&read_conf;
-
-our %config;
-our $do;       # statements from argv
-our @zones;    # list of zones from argv
-our $master_dir      = $config{master_dir};
-our $bind_dir        = $config{bind_dir};
-our $conf_dir        = $config{zone_conf_dir};
-our $sign_alert_time = $config{sign_alert_time};
-our $indexzone       = $config{indexzone};
-our $key_counter_end = $config{key_counter_end};
-our $ablauf_zeit     = $config{abl_zeit};
-
-&read_argv;
-
-# completed the program, if not a valid zones was handed over
-unless (@zones) { exit; }
-
-if ($do eq "rm") { &rm_keys; exit; }
-if ($do eq "ck") { &ck_zone; }
-if ($do eq "ksk") { &creat_ksk; }
-
-&creat_zsk;
-&post_creat;
-
-__END__
-
-=pod
-
-=head1 NAME
-
-dnssec-keytool
-
-=head1 SYNOPSIS
-
-dnssec-keytool <option> zone
-
-=head1 DESCRIPTION
--- a/dnstools.conf	Tue Dec 21 17:00:11 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-bind_dir = /etc/bind			# bind-Hauptverzeichnis
-master_dir = /etc/bind/master		# Verzeichnis für die einzelnen Zonen-Verzeichnisse
-zone_conf_dir = /etc/bind/zones.d	# Verzeichnis für die Zonen-Konfigurationdateien
-
-key_counter_end = 15			# Anzahl der Signierungen bis zum Key-Rollover
-sign_alert_time = 168			# Warn-Zeitraum vor dem Ablauf einer Zone-Signatur in h
-abl_zeit = 24				# Dauer des Key-Rollover (2 Schluessel) in h
-
-secondary = hh.schlittermann.de
-primary = pu.schlittermann.de
-
-indexzone = idx.net.schlittermann.de	# Name der Indexdatei
-
-#this_host
-#this_ip
-#this_domain
-#secondary_ip
-#hostmaster
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dnstools.conf.example	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,21 @@
+bind_dir = ./bind			# bind's config directory (named.conf)
+master_dir = ./bind/master		# location of the master zone directories
+zone_conf_dir = ./bind/zones.d		# location of configs for the zones
+
+key_counter_end = 15			# Anzahl der Signierungen bis zum Key-Rollover
+sign_alert_time = 168			# Warn-Zeitraum vor dem Ablauf einer Zone-Signatur in h
+abl_zeit = 24				# Dauer des Key-Rollover (2 Schluessel) in h
+
+secondary = hh.schlittermann.de
+primary   = pu.schlittermann.de
+hostmaster = hostmaster@schlittermann.de
+
+indexzone = idx.net.schlittermann.de	# Name der Indexdatei
+
+template_dir = ./templates		
+
+#this_host
+#this_ip
+#this_domain
+#secondary_ip
+#hostmaster
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/DNStools/.perltidyrc	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,1 @@
+../../.perltidyrc
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/DNStools/Config.pm	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,82 @@
+package DNStools::Config;
+
+use strict;
+use warnings;
+
+use base "Exporter";
+
+our $VERSION   = 0.0;
+our @EXPORT_OK = qw(get_config);
+
+sub get_config(@) {
+
+    # read configuration
+    my @configs =
+        @_                          ? @_
+      : defined $ENV{DNSTOOLS_CONF} ? $ENV{DNSTOOLS_CONF}
+      :   ("dnstools.conf", "$ENV{HOME}/.dnstools.conf", "/etc/dnstools.conf");
+    my %config;
+
+    # the first config FILE
+    my ($_) = grep { -f } @configs;
+
+    die "no config file found, searched for @configs\n" if not $_;
+    open(my $cf, $_) or die "Can't open $_: $!\n";
+
+    while (<$cf>) {
+        s/#.*//;
+        s/\s//g;
+        next unless length;
+        my ($cname, $ccont) = split(/\s*=\s*/, $_, 2);
+        $config{$cname} = $ccont;
+    }
+
+    # now merge the config hashes
+    foreach my $o (grep { ref eq "HASH" } @configs) {
+        %config =
+          (%config, map { $_ => $o->{$_} } grep { defined $o->{$_} } keys %$o);
+    }
+    return %config;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+    DNStools::Config - config parser
+
+=head1 SYNOPSIS
+
+    use DNStools::Config qw(get_config);
+    %config = get_config();
+    %config = get_config($file1, $file2, ...);
+
+=head1 DESCRIPTION
+
+Simple config file parser. The format is simple:
+
+    key = value
+
+All spaces are ignored.
+
+=head1 FUNCTIONS
+
+=over
+
+=item B<get_config>(I<list of config files>)
+
+Read the first file of the list (or dies if none of the files is found).
+Returns a hash with the config keys and values.
+
+If the list is empty, the configuration file is search in some default
+locations: C<$DNSTOOLS_CONF>, F<./dnstools.conf>,
+F<$HOME/.dnstools.conf>, F</etc/dnstools.conf>.
+
+=back
+
+=cut
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t/00-config.t	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,45 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+use Pod::Coverage;
+use File::Temp;
+
+BEGIN {
+    use_ok "DNStools::Config" => qw(get_config);
+}
+
+can_ok("DNStools::Config" => "get_config");
+
+eval { get_config("xxx|xxx", "yyy|yyy") };
+ok($@, "dies on missing config");
+
+my ($tmp, %cf);
+
+# prepare some simple sample config
+$tmp = File::Temp->new();
+print {$tmp} <<__EOF;
+# comment
+abc = xyz
+other =    value with space
+__EOF
+close($tmp);
+
+# the files is specified, it should find the first 
+# existing
+%cf = get_config("xxx|xxx", $tmp->filename);
+ok(%cf, "got config");
+is($cf{abc} => "xyz", "simple value");
+is($cf{other} => "valuewithspace", "spaced value");
+
+# it should find the file specified in $ENV{DNSTOOLS_CONF}
+$ENV{DNSTOOLS_CONF} = $tmp->filename;
+%cf = ();
+%cf = get_config("xxx|xxx", $tmp->filename);
+ok(%cf, "got config from \$DNSTOOLS_CONF");
+is($cf{abc} => "xyz", "simple value");
+is($cf{other} => "valuewithspace", "spaced value");
+
+
+done_testing();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t/01-zone-mk	Sun Jan 23 00:30:15 2011 +0100
@@ -0,0 +1,39 @@
+use strict;
+use warnings;
+
+use Test::More;
+use File::Temp;
+use Net::LibIDN qw(:all);
+
+my $CMD = "perl -Mblib blib/script/zone-mk";
+
+system("$CMD -h &>/dev/null");
+is($? => 0, "exit on help");
+
+system("$CMD -m &>/dev/null");
+is($? => 0, "exit on man");
+
+system("$CMD &>/dev/null");
+ok($? > 0, "error on missing args");
+
+my $master_dir = File::Temp->newdir;
+my $config_dir = File::Temp->newdir;
+my $tmp = File::Temp->new;
+$ENV{DNSTOOLS_CONF} = $tmp->filename;
+
+print $tmp <<__EOF;
+master_dir = $master_dir
+zone_conf_dir = $config_dir
+hostmaster = hostmaster\@schlittermann.de
+template_dir = ./templates		
+__EOF
+
+system("$CMD xxx müller.de &>/dev/null");
+is($? => 0, "created zone");
+
+my $domain = idn_to_ascii("müller.de", "UTF-8");
+ok(-s "$master_dir/$domain/$domain", "zone file $domain");
+ok(-s "$config_dir/$domain", "config file $domain");
+
+
+done_testing;
--- a/templates/named.config	Tue Dec 21 17:00:11 2010 +0100
+++ b/templates/named.config	Sun Jan 23 00:30:15 2011 +0100
@@ -1,10 +1,10 @@
-zone "<domain>" {
-// Start: <start>
-// Invoice: <customer>
-// UTF8: <utf8domain>
+[% USE iso = date(format => '%Y-%m-%dT%H:%M:%S') -%]
+zone "[% domain %]" {
+// Start: [% iso.format(now) %]
+// Invoice: [% customer %]
+// UTF8: [% utf8domain %]
 	type master;
-	file "<file>";
-	allow-transfer { localhost; <primary_ip>; <secondary_ip>; };
+	file "[% zonefile %]";
+	allow-transfer { localhost; slaves; };
 	allow-query { any; };
-	also-notify { <primary_ip>; };
 };
--- a/templates/named.zone	Tue Dec 21 17:00:11 2010 +0100
+++ b/templates/named.zone	Sun Jan 23 00:30:15 2011 +0100
@@ -1,17 +1,19 @@
-$ORIGIN <domain>.
+[% USE iso = date(format => '%Y-%m-%dT%H:%M:%S') %]
+[% USE serial = date(format => '%Y%m%d00') -%]
+$ORIGIN [% domain %].
 $TTL 1d
-@               IN SOA <primary>. <hostmaster>. (
-                <time>		; serial
+@               IN SOA [% primary %]. [% hostmaster %]. (
+                [% serial.format(now) %]	; serial
                 1d              ; refresh
                 2h              ; retry
                 7d              ; expire
-                1d              ; default ttl
+                5m              ; negative ttl
 )
 
-                IN TXT          "invoice: <customer>"
-                IN TXT          "start: <start>"
-                IN TXT          "utf8: <utf8domain>"
+                IN TXT          "invoice: [% customer %]"
+                IN TXT          "start: [% now %] [% iso.format(now) %]
+                IN TXT          "utf8: [% utf8domain %]"
 
-                IN NS           <primary>.
-                IN NS           <secondary>.
+                IN NS           [% primary %].
+                IN NS           [% secondary %].
 
--- a/update-serial.pl	Tue Dec 21 17:00:11 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,583 +0,0 @@
-#!/usr/bin/perl 
-
-use strict;
-use warnings;
-use FindBin;
-use File::Basename;
-
-sub del_double {
-
-    # remove duplicate entries
-    my %all;
-    grep { $all{$_} = 0 } @_;
-    return (keys %all);
-}
-
-sub read_conf {
-
-    # read the configuration
-    my @configs = ("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf");
-    our %config;
-
-    for (grep { -f } @configs) {
-        open(CONFIG, $_) or die "Can't open $_: $!\n";
-    }
-    unless (seek(CONFIG, 0, 0)) {
-        die "Can't open config (searched: @configs)\n";
-    }
-    while (<CONFIG>) {
-        chomp;
-        s/#.*//;
-        s/\t//g;
-        s/\s//g;
-
-        next unless length;
-        my ($cname, $ccont) = split(/\s*=\s*/, $_, 2);
-        $config{$cname} = $ccont;
-    }
-    close(CONFIG);
-}
-
-sub add_argv {
-    # checked whether the zones in argv are managed zones and
-    #inserted them into the list new_serial
-    our @new_serial;
-    our $master_dir;
-    my $zone;
-
-    for (@ARGV) {
-        chomp($zone = `idn --quiet "$_"`);
-        if (-e "$master_dir/$zone/$zone") {
-            push @new_serial, $zone;
-        }
-    }
-}
-
-sub changed_zone {
-    our $master_dir;
-    our @new_serial;
-
-    for (<$master_dir/*>) {
-        my $zone = basename($_);
-
-        if (-e "$master_dir/$zone/.stamp") {
-            my $stamptime = (-M "$master_dir/$zone/.stamp");
-            my $filetime  = (-M "$master_dir/$zone/$zone");
-            if ($stamptime > $filetime) {
-                push @new_serial, $zone;
-                print " * $zone: zonedatei wurde geaendert\n";
-            }
-        }
-        else {
-            print " * $zone: keine .stamp-datei gefunden\n"
-              ;    # NOCH IN NEW_SERIAL PUSHEN
-            push @new_serial, $zone;
-        }
-    }
-
-}
-
-sub sign_end {
-    our $sign_alert_time;    # the time between the end and the new signing
-                             # (see external configuration)
-    our $master_dir;
-    our @new_serial;
-
-    # erzeugt $time (die zeit ab der neu signiert werden soll)
-    chomp(my $unixtime = `date +%s`);
-    $unixtime = $unixtime + (3600 * $sign_alert_time);
-    my $time = `date -d \@$unixtime +%Y%m%d%H`;
-
-    ## vergleicht fuer alle zonen im ordner $master_dir mit einer
-    ## <zone>.signed-datei den zeitpunkt in $time mit dem ablaufdatum der
-    ## signatur, welcher aus der datei <zone>.signed ausgelesen wird.
-    for (<$master_dir/*>) {
-        s#($master_dir/)(.*)#$2#;
-        my $zone = $_;
-
-        if (-e "$master_dir/$zone/$zone.signed") {
-            open(ZONE, "$master_dir/$zone/$zone.signed");
-            my @zone_sig_content = <ZONE>;
-            close(ZONE);
-
-            for (@zone_sig_content) {
-                if (m#SOA.*[0-9]{14}#) {
-                    s#.*([0-9]{10})([0-9]{4}).*#$1#;
-                    if ($_ < $time) {
-                        push @new_serial, $zone;
-                    }
-                }
-            }
-        }
-    }
-}
-
-sub sign_zone {
-
-    # signiert die zonen und erhoeht den wert in der keycounter-datei
-    our @new_serial;
-    our $master_dir;
-    my $zone;
-    my $kc;
-
-    for (&del_double(@new_serial)) {
-        $zone = $_;
-
-        unless (-e "$master_dir/$zone/.index.zsk") {
-            next;
-        }
-
-        chdir "$master_dir/$zone";
-        if (`dnssec-signzone $zone 2>/dev/null`) {
-            print " * $zone neu signiert \n";
-
-            # erhoeht den keycounter
-            if ("$master_dir/$zone/.keycounter") {
-                open(KC, "$master_dir/$zone/.keycounter");
-                $kc = <KC>;
-                close(KC);
-                $kc += 1;
-            }
-            else {
-                $kc = 1;
-            }
-            open(KC, ">$master_dir/$zone/.keycounter");
-            print KC $kc;
-            close(KC);
-        }
-        else { print "$zone konnte nicht signiert werden \n"; }
-    }
-}
-
-sub update_serial {
-    our $master_dir;
-    our @new_serial;
-    chomp(my $date = `date +%Y%m%d`);
-    my @new_content;
-    my $sdate;
-    my $scount;
-    my $serial;
-
-    for (&del_double(@new_serial)) {
-
-        # erhoeht den serial
-        my $zone        = $_;
-        my $file        = "$master_dir/$zone/$zone";
-        my @new_content = ();
-
-        open(SER, "<$file") or die "$file: $!\n";
-        for (<SER>) {
-            if (/^\s+(\d+)(\d{2})\s*;\s*serial/i) {
-                $sdate  = $1;
-                $scount = $2;
-                $serial = "$sdate$scount";
-                if ($date eq $sdate) {
-                    $scount++;
-                }
-                else {
-                    $sdate  = $date;
-                    $scount = "00";
-                }
-            }
-            if ($serial) {
-                s/$serial/$sdate$scount/;
-            }
-            push @new_content, $_;
-        }
-        close(SER);
-
-        open(RES, ">$file") or die "$file: $!\n";
-        print RES @new_content;
-        close(RES);
-        print " * $zone: serial erhoeht \n";
-
-        open(STAMP, ">$master_dir/$zone/.stamp")
-          or die "$master_dir/$zone/.stamp: $!\n";
-        close(STAMP);
-        print " * $zone: stamp aktualisiert \n";
-    }
-}
-
-sub mk_zone_conf {
-
-    # erzeugt eine named.conf-datei aus den entsprechenden vorlagen.
-    our $bind_dir;
-    our $conf_dir;
-
-    open(TO, ">$bind_dir/named.conf.zones")
-      or die "$bind_dir/named.conf.zones: $!\n";
-    while (<$conf_dir/*>) {
-        open(FROM, "$_") or die "$_: $! \n";
-        print TO <FROM>;
-        close(FROM);
-    }
-    close(TO);
-    print "** zonekonfiguration erzeugt\n";
-}
-
-sub update_index {
-
-    # aktualisiert die indexzone;
-    our @new_serial;
-    our $indexzone;
-    our $master_dir;
-    my @iz_content_old;
-    my @iz_content_new;
-
-    open(INDEXZONE, "$master_dir/$indexzone/$indexzone")
-      or die "$master_dir/$indexzone/$indexzone: $!\n";
-    @iz_content_old = <INDEXZONE>;
-    close(INDEXZONE);
-
-    for (@iz_content_old) {
-        unless (m#ZONE::#) {
-            push @iz_content_new, $_;
-        }
-    }
-
-    for my $dir (glob "$master_dir/*") {
-        my $zone     = basename($dir);
-        my $info_end = "::sec-off";
-
-        if (-e "$dir/.keycounter") {
-            $info_end = "::sec-on";
-        }
-
-        my $iz_line = "\t\tIN TXT\t\t\"ZONE::$zone$info_end\"\n";
-
-        push @iz_content_new, $iz_line;
-    }
-
-    open(INDEXZONE, ">$master_dir/$indexzone/$indexzone")
-      or die "$master_dir/$indexzone/$indexzone: $!\n";
-    print INDEXZONE @iz_content_new;
-    close(INDEXZONE);
-
-    # fuegt die index-zone in die liste damit der serial erhoet wird
-    push @new_serial, $indexzone;
-
-    print "** index-zone aktualisiert \n";
-}
-
-sub file_entry {
-
-    # prueft jede domain, die ein verzeichnis in $master_dir hat, ob sie
-    # dnssec nutzt.
-    # passt die eintraege in $config_file falls noetig an.
-    our $master_dir;
-    our $conf_dir;
-
-    while (<$master_dir/*>) {
-        s#($master_dir/)(.*)#$2#;
-        my $zone      = $_;
-        my $zone_file = "$master_dir/$zone/$zone";
-        my $conf_file = "$conf_dir/$zone";
-        my @c_content;
-
-        unless (-f "$conf_file") {
-            die "$conf_file: $! \n";
-        }
-
-        if (-e "$master_dir/$zone/.keycounter") {
-            open(FILE, "<$conf_file") or die "$conf_file: $!\n";
-            @c_content = <FILE>;
-            close(FILE);
-            for (@c_content) {
-                if (m{(.*)($zone_file)(";)}) {
-                    print
-                      " * zonekonfiguration aktualisiert ($2 ==> $2.signed)\n";
-                    $_ = "$1$2.signed$3\n";
-                }
-            }
-            open(FILE, ">$conf_file") or die "$conf_file: $!\n";
-            print FILE @c_content;
-            close(FILE);
-        }
-        else {
-            open(FILE, "<$conf_file") or die "$conf_file: $!\n";
-            @c_content = <FILE>;
-            close(FILE);
-            for (@c_content) {
-                if (m{(.*)($zone_file)\.signed(.*)}) {
-                    print
-                      " * zonekonfiguration aktualisiert ($2.signed ==> $2)\n";
-                    $_ = "$1$2$3\n";
-                }
-            }
-            open(FILE, ">$conf_file") or die "$conf_file: $!\n";
-            print FILE @c_content;
-            close(FILE);
-        }
-    }
-}
-
-sub server_reload {
-    if (`rndc reload`) { print "** reload dns-server \n" }
-}
-
-sub to_begin_ro {
-
-    # gibt alle zonen mit abgelaufenen keycounter in die liste @begin_ro_list
-    our @begin_ro_list;
-    our $master_dir;
-    our $key_counter_end;
-    our @new_serial;
-    my $zone;
-
-    while (<$master_dir/*>) {
-        chomp($zone = $_);
-        my $key;
-
-        unless (-f "$zone/.keycounter") { next; }
-
-        open(KEY, "$zone/.keycounter") or die "$zone/.keycounter: $!\n";
-        $key = <KEY>;
-        close(KEY);
-
-        # vergleicht den wert aus der keycount-datei mit dem wert aus der
-        #dnstools.conf (key_counter_end)
-        if ($key_counter_end <= $key) {
-            $zone =~ s#($master_dir/)(.*)#$2#;
-            push @begin_ro_list, $zone;
-        }
-    }
-}
-
-sub to_end_ro {
-
-    # funktion ueberprueft ob ein keyrollover fertig ist
-    # die bedingung dafuer ist das:
-    # - eine datei .index.zsk vorhanden ist
-    # - die datei .index.zsk vor mehr x stunden geaendert wurde
-    # - die datei .index.zsk ueber mehr als zwei zeilen gross ist
-    our $master_dir;
-    our @end_ro_list;
-    our $ablauf_zeit;
-    chomp(my $now_time = `date +%s`);
-
-    for (<$master_dir/*>) {
-        my $zone = $_;
-        $zone =~ s#($master_dir/)(.*)#$2#;
-
-        my @index = ();
-        my $index_wc;
-        my @status;
-
-        # prueft nach der ".index.zsk"-datei und erstellt den zeitpunkt
-        # an dem das key-rollover endet. - $status[9]
-        if (-e "$master_dir/$zone/.index.zsk") {
-            @status = stat("$master_dir/$zone/.index.zsk");
-            $status[9] += (3600 * $ablauf_zeit);
-        }
-        else { next; }
-
-        # $status[9] ist der zeitpunkt an dem der key-rollover endet
-        # prueft ob das key-rollover-ende erreicht ist
-        unless ($status[9] < $now_time) { next; }
-
-        # prueft die anzahl der schluessel in der .index.zsk
-        open(INDEX, "$master_dir/$zone/.index.zsk")
-          or die "$master_dir/$zone/.index.zsk: $!\n";
-        @index    = <INDEX>;
-        $index_wc = @index;
-        close(INDEX);
-        if ($index_wc > 1) { push @end_ro_list, $zone; }
-    }
-}
-
-sub begin_ro {
-
-    # anfang des key-rollovers
-    our @begin_ro_list;
-    our $master_dir;
-    our @new_serial;
-
-    for (&del_double(@begin_ro_list)) {
-
-        #erzeugt zsks
-        my $zone = $_;
-        my $zpf  = "$master_dir/$zone";
-        my @index;
-
-        chdir "$zpf" or die "$zpf: $!\n";
-        my $keyname = `dnssec-keygen -a RSASHA1 -b 512 -n ZONE $zone`;
-
-        open(INDEX, ".index.zsk") or die "$zpf/.index.zsk: $!\n";
-        @index = <INDEX>;
-        close(INDEX);
-
-        push @index, $keyname;
-        if (@index > 2) { shift(@index); }
-
-        open(INDEX, ">.index.zsk") or die "$zpf/.index.zsk: $!\n";
-        print INDEX @index;
-        close(INDEX);
-
-        chomp($keyname);
-        print " * $zone: neuer ZSK $keyname erstellt\n";
-
-        open(KC, ">.keycounter") or die "$zpf/keycounter: $!\n";
-        print KC "0";
-        close(KC);
-
-        &kill_useless_keys($zone);
-        &key_to_zonefile($zone);
-        push @new_serial, $zone;
-    }
-}
-
-sub key_to_zonefile {
-
-    # die funktion fugt alle schluessel in eine zonedatei
-    our $master_dir;
-    my $zone = $_[0];
-    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);
-}
-
-sub kill_useless_keys {
-
-    # die funktion loescht alle schluessel die nicht in der index.zsk
-    # der uebergebenen zone stehen
-    our $master_dir;
-    my $zone    = $_[0];
-    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>;
-
-    # kuerzt die schluessel-bezeichnung aus der indexdatei auf die id um sie
-    # besser vergleichen zu koennen.
-    for (@keylist) {
-        chomp;
-        s#K.*\+.*\+(.*)#$1#;
-    }
-
-    # prueft alle schluesseldateien (ksk, zsk) ob sie in der jeweiligen
-    # indexdatei beschrieben sind. wenn nicht werden sie geloescht.
-    for (`ls $master_dir/$zone/K*[key,private]`) {
-        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: Schluessel $1 entfernt \n";
-            }
-        }
-    }
-}
-
-sub end_ro {
-    our @end_ro_list;
-    our $master_dir;
-    our @new_serial;
-    my @content;
-
-    for (@end_ro_list) {
-        my $zone  = $_;
-        my $count = 0;
-        my @content;
-        my $last_key;
-
-        open(INDEX, "<$master_dir/$zone/.index.zsk");
-        @content = <INDEX>;
-        close(INDEX);
-
-        for (@content) {
-            $count++;
-            $last_key = $_;
-        }
-        if ($count > 1) {
-            open(INDEX, ">$master_dir/$zone/.index.zsk");
-            print INDEX $last_key;
-            close(INDEX);
-        }
-        &kill_useless_keys($zone);
-        &key_to_zonefile($zone);
-        push @new_serial, $zone;
-    }
-}
-
-&read_conf;
-
-our %config;
-our @new_serial;       # liste fuer neuen serial
-our @begin_ro_list;    # liste mit zonen deren key-rollover beginnt
-our @end_ro_list;      # liste mit zonen deren key-rollover fertig ist
-our $master_dir      = $config{master_dir};
-our $bind_dir        = $config{bind_dir};
-our $conf_dir        = $config{zone_conf_dir};
-our $sign_alert_time = $config{sign_alert_time};
-our $indexzone       = $config{indexzone};
-our $key_counter_end = $config{key_counter_end};
-our $ablauf_zeit     = $config{abl_zeit};
-
-&add_argv;
-&changed_zone;
-&sign_end;
-
-&to_begin_ro;    # prueft nach beginnenden rollover-verfahren
-&to_end_ro;      # prueft nach endenden rollover-verfahren
-
-if (@begin_ro_list) {
-    &begin_ro;    # eine rollover-beginn-sequenz
-}
-
-if (@end_ro_list) {
-    &end_ro;      # eine rollover-end-squenz
-}
-
-if (@new_serial) {
-    &update_index;     # index zone aktuallisieren
-    &update_serial;    # serial aktuallisieren
-    &sign_zone;        # zone signieren
-}
-
-&file_entry;  # bearbeitet die file-eintraege der konfigurations-datei
-&mk_zone_conf; # konfiguration zusammenfuegen
-&server_reload; # server neu laden
-
-
-
-__END__
-
-=pod
-
-=head1 TITLE
-
-update-serial
-
-=head1 SYNTAX
-
-update-serial
-
-=head1 BESCHREIBUNG
-
-=cut
--- a/zone-ls.pl	Tue Dec 21 17:00:11 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-#! /usr/bin/perl
-
-use v5.10;
-use strict;
-use warnings;
-use Pod::Usage;
-use File::Basename;
-use FindBin;
-use Time::Local;
-use Getopt::Long;
-
-my %config;
-my $opt_expiry = undef;
-
-MAIN: {
-    my %info;    # will hold the information we collected
-
-    GetOptions(
-        "e|expiry" => \$opt_expiry,
-        "h|help"   => sub { pod2usage(-exit => 0, -verbose => 1) },
-        "m|man"    => sub {
-            pod2usage(
-                -exit      => 0,
-                -verbose   => 2,
-                -noperldoc => system("perldoc -V &>/dev/null")
-            );
-        },
-    ) or pod2usage;
-
-    {    # find and read/parse the config (could use some common config parser)
-        my @configs = ("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf");
-        ($_) = grep { -f } @configs;
-        open(my $config, $_) or die "Can't open $_: $!\n";
-
-        while (<$config>) {
-            chomp;
-            s/#.*//;
-            s/\s//g;
-            my ($k, $v) = split(/\s*=\s*/, $_, 2) or next;
-            $config{$k} = $v;
-        }
-    }
-
-    die "$config{master_dir}: $!\n" if not -d $config{master_dir};
-
-    foreach my $dir (grep { -d } glob "$config{master_dir}/*") {
-
-        my $zone = basename($dir);
-        $info{$zone} = { status => "OK" };
-
-        if (not -f "$dir/.index.zsk") {
-            $info{$zone}{zsk}    = 0;
-            $info{$zone}{ksk}    = 0;
-            $info{$zone}{kc}     = 0;
-            $info{$zone}{end}    = "-";
-            $info{$zone}{expiry} = undef;
-            next;
-        }
-
-        # prueft wie viele zsks genutzt werden
-        {
-            open(my ($fh), $_ = "<$dir/.index.zsk")
-              or die "Can't open $_: $!\n";
-            () = <$fh>;
-            $info{$zone}{zsk} = $.
-        }
-
-        # prueft wie viele ksks genutzt werden
-        {
-            open(my ($fh), $_ = "<$dir/.index.ksk")
-              or die "Can't open $_: $!\n";
-            () = <$fh>;
-            $info{$zone}{ksk} = $.
-        }
-
-        # prueft wie oft die schluessel zum signieren genutzt wurden
-        {
-            open(my ($fh), $_ = "<$dir/.keycounter")
-              or die "Can't open $_: $!\n";
-            chomp($info{$zone}{kc} = <$fh>);
-        }
-
-        # prueft das ablaufdatum
-        if (!-f "$dir/$zone.signed") {
-            $info{$zone}{end} = "-";
-            next;
-        }
-
-        open(my ($fh), $_ = "<$dir/$zone.signed") or die "Can't open $_: $!\n";
-        while (<$fh>) {
-            next if not /RSIG.*SOA.*\s
-				(?<year>\d\d\d\d)
-				(?<mon>\d\d)
-				(?<day>\d\d)
-				(?<hour>\d\d)
-				(?<min>\d\d)\d+\s\(/ix;
-            $info{$zone}{end} = "$+{day}.$+{mon}.$+{year} $+{hour}:$+{min}";
-            $info{$zone}{expiry} =
-              timelocal(0, $+{min}, $+{hour}, $+{day}, $+{mon} - 1, $+{year});
-        }
-    }
-
-    {    # output
-
-        my $sort_by =
-          $opt_expiry
-          ? sub { ($info{$a}{expiry} // 2**64) <=> ($info{$b}{expiry} // 2**64) }
-          : sub { $a cmp $b };
-
-        my $format_h = "%-35s %-8s %1s/%1s %3s %7s\n";
-        my $format_l = "%-35s %-8s %1d/%1d %5d %19s\n";
-
-        printf $format_h => qw(Domain Status ZSK KSK Used Sig-end);
-
-        foreach my $zone (sort $sort_by keys %info) {
-            printf $format_l => $zone,
-              @{ $info{$zone} }{qw(status zsk ksk kc end)};
-        }
-    }
-}
-
-__END__
-
-=head1 NAME
-
- zone-ls -- lists all zones
-
-=head1 SYNOPSIS
-
- zone-ls [-e|--expiry]
-
-=head1 DESCRIPTION
-
-This B<zone-ls> lists all zones under control of our dnstools suite. The output is ordered by domain name.
-
-=head1 OPTIONS
-
-=over
-
-=item B<-e>|B<--expiry>
-
-Order the output by expiry date. The sooner the key expires, the more top the
-domain is listed.
-
-=back
-
-Additionally the common B<-h>|B<--help>|B<-m>|B<--man> options, which should be
-self explanatory.
-
-=head1 AUTHORS
-
-L<andre.suess@pipkin.cc>
-
-=cut
-
-# vim:ts=4 sw=4 ai si aw:
--- a/zone-mk.pl	Tue Dec 21 17:00:11 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-#!/usr/bin/perl 
-
-use warnings;
-use strict;
-use FindBin;
-
-if (@ARGV < 2) {
-    print "usage: zone-mk kundennummer domain ... \n";
-    exit 1;
-}
-
-# oeffnet Konfigurations- und Templatefiles - relativ oder absolut
-my @configs = ("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf");
-my @templc = (
-    "$FindBin::Bin/templates/named.config",
-    "/etc/dnstools/templates/named.config"
-);
-my @templz =
-  ("$FindBin::Bin/templates/named.zone", "/etc/dnstools/templates/named.zone");
-my %config;
-
-for (grep { -f } @configs) {
-    open(CONFIG, $_) or die "Can't open $_: $!\n";
-}
-unless (seek(CONFIG, 0, 0)) {
-    die "Can't open config (searched: @configs)\n";
-}
-
-for (grep { -f } @templc) {
-    open(TEMPCONF, $_) or die "Can't open $_: $!\n";
-}
-unless (seek(TEMPCONF, 0, 0)) {
-    die "Can't open template (searched: @templc)\n";
-}
-
-for (grep { -f } @templz) {
-    open(TEMPZONE, $_) or die "Can't open $_: $!\n";
-}
-unless (seek(TEMPZONE, 0, 0)) {
-    die "Can't open template (searched: @templz)\n";
-}
-
-while (<CONFIG>) {
-    chomp;
-    s/#.*//;
-    s/\t//g;
-    s/\s//g;
-    next unless length;
-    my ($cname, $ccont) = split(/\s*=\s*/, $_, 2);
-    $config{$cname} = $ccont;
-}
-close(CONFIG);
-
-my $primary       = $config{primary};
-my $secondary     = $config{secondary};
-my $zone_conf_dir = $config{zone_conf_dir};
-my $master_dir    = $config{master_dir};
-my $customer      = shift @ARGV;
-chomp(my $primary_ip   = `dig +short $primary`);
-chomp(my $secondary_ip = `dig +short $secondary`);
-chomp(my $this_host    = `hostname -f`);
-chomp(my $this_ip      = `hostname -i`);
-chomp(my $this_domain  = `hostname -d`);
-chomp(my $time         = `date +%Y%m%d00`);
-chomp(my $start        = `date -I`);
-my $hostmaster = "hostmaster.$this_domain";
-
-unless (-d $master_dir and -r $master_dir) {
-    die "$master_dir: $!\n";
-}
-
-unless (-d $zone_conf_dir and -r $zone_conf_dir) {
-    die "$master_dir: $!\n";
-}
-
-# legt fuer jede domain in @ARGV ein verzeichnis in $master_dir an.
-# schreibt aus den angegebenen templates die dateien $zonefile und $config
-# in die entsprechenden verzeichnisse.
-for (@ARGV) {
-
-    chomp(my $domain = `idn --quiet "$_"`);
-    my $zonefile   = "$master_dir/$domain/$domain";
-    my $config     = "$zone_conf_dir/$domain";
-    my $utf8domain = "$_";
-
-    unless (-d "$master_dir/$domain") {
-        `mkdir $master_dir/$domain`;
-    }
-
-    if (-f $zonefile) {
-        $zonefile =~ s#/.*/##;
-        print "$zonefile exists. Skipping $domain\n";
-        next;
-    }
-    if (-f $config) {
-        $config =~ s#/.*/##;
-        print "$config exists. Skipping $domain\n";
-        next;
-    }
-
-    print "$domain ($_) for $customer \n";
-
-    my @tempzone = <TEMPZONE>;
-    for (@tempzone) {
-        s#<start>#$start#;
-        s#<domain>#$domain#;
-        s#<time>#$time#;
-        s#<primary>#$primary#;
-        s#<secondary>#$secondary#;
-        s#<hostmaster>#$hostmaster#;
-        s#<customer>#$customer#;
-        s#<utf8domain>#$utf8domain#;
-    }
-
-    open(ZONEOUT, ">$zonefile");
-    print ZONEOUT @tempzone;
-    close(ZONEOUT);
-
-    my @tempconf = <TEMPCONF>;
-    for (@tempconf) {
-        s#<domain>#$domain#;
-        s#<start>#$start#;
-        s#<customer>#$customer#;
-        s#<utf8domain>#$utf8domain#;
-        s#<file>#$master_dir/$domain/$domain#;
-        s#<primary_ip>#$primary_ip#;
-        s#<secondary_ip>#$secondary_ip#;
-    }
-
-    open(CONFOUT, ">$config");
-    print CONFOUT @tempconf;
-    close(CONFOUT);
-}
--- a/zone-rm.pl	Tue Dec 21 17:00:11 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-#!/usr/bin/perl 
-
-use warnings;
-use strict;
-use File::Path;
-use FindBin;
-
-# liest die Konfiguration ein
-my @configs = ("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf");
-my %config;
-
-foreach (grep { -f } @configs) {
-    open(CONFIG, $_) or die "Can't open $_: $!\n";
-}
-
-unless (seek(CONFIG, 0, 0)) {
-    die "Can't open config (searched: @configs)\n";
-}
-
-while (<CONFIG>) {
-    chomp;
-    s/#.*//;
-    s/\t//g;
-    s/\s//g;
-    next unless length;
-    my ($cname, $ccont) = split(/\s*=\s*/, $_, 2);
-    $config{$cname} = $ccont;
-}
-close(CONFIG);
-
-my $master_dir = $config{"master_dir"};
-my $conf_dir   = $config{"zone_conf_dir"};
-
-for (@ARGV) {
-    chomp(my $zone = `idn --quiet "$_"`);
-
-    if (-d "$master_dir/$zone") {
-        rmtree "$master_dir/$zone/"
-          and print "zone-dir for $zone removed\n";
-    }
-    else {
-        print "$master_dir/$zone: $!\n";
-    }
-
-    if (-e "$conf_dir/$zone") {
-        unlink "$conf_dir/$zone"
-          and print "configuration-file for $zone removed\n";
-    }
-    else {
-        print "$conf_dir/$zone: $!\n";
-    }
-}