install scripts to sbin; added some mor dependencies
authorMatthias Förste <foerste@schlittermann.de>
Mon, 06 Jun 2011 12:48:00 +0200
changeset 132 1306901e3462
parent 130 5578cb7933c1
child 133 149b28d40c32
install scripts to sbin; added some mor dependencies
Build.PL
bin/.perltidyrc
bin/dnssec-keytool
bin/update-serial
bin/zone-ls
bin/zone-mk
bin/zone-rm
sbin/.perltidyrc
sbin/dnssec-keytool
sbin/update-serial
sbin/zone-ls
sbin/zone-mk
sbin/zone-rm
--- a/Build.PL	Mon Jun 06 09:30:17 2011 +0200
+++ b/Build.PL	Mon Jun 06 12:48:00 2011 +0200
@@ -33,9 +33,14 @@
     requires       => {
         perl          => "5.10.0",
         "Net::LibIDN" => "0",
-        "Template"    => "0"
+        "Template"    => "0",
+        "Net::DNS"    => "0",
+        "Net::DNS::SEC"    => "0",
     },
     script_files => [glob "bin/*"],    # avoid .swp files
+    sbin_files => { map { $_ => $_ unless /.(bak|orig)$/ } glob "sbin/*" },
 );
 
+$build->add_build_element('sbin');
+$build->install_path('sbin' => $build->original_prefix($build->installdirs) . '/sbin');
 $build->create_build_script;
--- a/bin/.perltidyrc	Mon Jun 06 09:30:17 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../.perltidyrc
\ No newline at end of file
--- a/bin/dnssec-keytool	Mon Jun 06 09:30:17 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,349 +0,0 @@
-#! /usr/bin/perl
-
-use v5.10;
-use warnings;
-use strict;
-use File::Temp;
-use Getopt::Long;
-use Pod::Usage;
-use File::Basename;
-use Net::LibIDN qw(:all);
-use if $ENV{DEBUG} => "Smart::Comments";
-use DNStools::Config qw(get_config);
-
-my $ME = basename $0;
-
-sub rm_keys($@);
-sub check_zone($@);
-sub create_ksk($@);
-sub create_zsk(@);
-sub post_create($@);
-
-my $CHARSET = "UTF-8";
-my %cf;
-
-MAIN: {
-
-    %cf = get_config();
-    my $cmd;
-
-    system("command -v dnssec-keygen &>/dev/null");
-    die "$ME: command 'dnssec-keygen' not found in $ENV{PATH}\n" if $?;
-
-    GetOptions(
-        "zsk" => sub { push @$cmd => "zsk" },
-        "ksk" => sub { push @$cmd => "ksk" },
-        "rm"  => sub { push @$cmd => "rm" },
-        "check" => sub { $cmd = "check" },
-        "h|help" => sub { pod2usage(-exit => 0, -verbose => 1) },
-        "m|man"  => sub {
-            pod2usage(
-                -exit      => 0,
-                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
-                # do what you expect ( it still returns 0 on debian squeeze with
-                # dash as system shell even if cannot find the command in $PATH)
-                -noperldoc => system('perldoc -V >/dev/null 2>&1'),
-                -verbose   => 2
-            );
-        },
-      )
-      and @ARGV
-      and @$cmd == 1
-      and $cmd = $cmd->[0]
-      or pod2usage;
-
-    # checks the zones in argv if they're managed ones
-    my @zones;
-    foreach my $utf8zone (@ARGV) {
-        my $zone = idn_to_ascii($utf8zone, $CHARSET);
-
-        die "zone $zone is not managed\n"
-          if not -f "$cf{master_dir}/$zone/$zone";
-
-        push @zones, $zone;
-    }
-
-    given ($cmd) {
-        when ("zsk")   { exit create_zsk(@zones) };
-        #when ("ksk")   { return create_ksk(@zones) };
-        #when ("check") { return check_zone(@zones) };
-        #when ("rm")    { return rm_keys(@zones) };
-	default		{ die "not implemented\n" };
-    };
-}
-
-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 @zones = @_;
-
-    my $keyname;
-
-    foreach my $zone (@zones) {
-        my $dir  = "$cf{master_dir}/$zone";
-
-        chomp($keyname = `cd $dir && dnssec-keygen -a RSASHA1 -b 512 -n ZONE $zone`);
-
-	my @index;
-	open(my $idx, "+>>", "$dir/.index.zsk") or die "Can't open $dir/.index.zsk: $!\n";
-	seek($idx, 0, 0);
-	chomp(@index = <$idx>);
-
-        push @index, $keyname;
-	shift @index if @index > 2;
-
-	truncate($idx, 0);
-	print $idx join "\n" => @index, "";
-	close($idx);
-
-        say "$zone: new ZSK $keyname";
-
-        open(my $kc, ">", "$dir/.keycounter") or die "$dir/.keycounter: $!\n";
-        print $kc "0\n";
-        close($kc);
-    }
-}
-
-sub check_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 - key management
-
-=head1 SYNOPSIS
-
-    dnssec-keytool {--zsk|--ksk|--rm|--check} zone...
-
-=head1 DESCRIPTION
-
-Blabla.
-
-=head1 OPTIONS
-
-=over
-
-=item B<--zsk> 
-
-Create a new ZSK for the zones.
-
-=item B<--ksk>
-
-Create a new KSK for the zones.
-
-=item B<--rm>
-
-Remote all key material from the zones.
-
-=item B<--check>
-
-???
-
-=back
-
-=cut
-
-# vim:sts=4 sw=4 aw ai sm:
--- a/bin/update-serial	Mon Jun 06 09:30:17 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-#!/usr/bin/perl -w
-
-#    Copyright (C) 2011 Matthias Förste
-#    Copyright (C) 2010, 2011 Heiko Schlittermann
-#    Copyright (C) 2010 Andre Süß
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-#    Matthias Förste <foerste@schlittermann.de>
-
-=encoding utf8
-=cut
-
-use v5.10;
-use strict;
-use warnings;
-
-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);
-use DNStools::UpdateSerial;
-
-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,
-                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
-                # do what you expect ( it still returns 0 on debian squeeze with
-                # dash as system shell even if cannot find the command in $PATH)
-                -noperldoc => system('perldoc -V >/dev/null 2>&1')
-            );
-        }
-    ) or pod2usage;
-
-    # merge the config and the defined options from commandline
-    my @configs = ( "dnstools.conf", "$ENV{HOME}/.dnstools.conf",
-        "/etc/dnstools.conf");
-    unshift @configs, $ENV{DNSTOOLS_CONF} if defined $ENV{DNSTOOLS_CONF};
-    %config = get_config @configs, \%opt;
-
-    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)) {
-#        say "XXX: candidate $zone";
-        update_serial($zone);
-        sign($zone) if dnssec_enabled($zone, "$config{master_dir}/$config{indexzone}/$config{indexzone}");
-#        say "XXX: $zone should be signed" if dnssec_enabled($zone, "$config{master_dir}/$config{indexzone}/$config{indexzone}");
-    }
-
-    file_entry;
-    mk_zone_conf($config{bind_dir}, $config{zone_conf_dir});
-    server_reload;
-
-}
-
-__END__
-
-=pod
-
-=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>
-
-TODO
-
-=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 AUTHORS
-
-Matthias Förste L<<foerste@schlittermann.de>>, Heiko Schlittermann L<<hs@schlittermann.de>>, Andre Süss L<<andre.suess@pipkin.cc>>
-
-=cut
-
-# vim:sts=4 sw=4 aw ai sm:
--- a/bin/zone-ls	Mon Jun 06 09:30:17 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-#! /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 %cf;
-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,
-                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
-                # do what you expect ( it still returns 0 on debian squeeze with
-                # dash as system shell even if cannot find the command in $PATH)
-                -noperldoc => system('perldoc -V >/dev/null 2>&1')
-            );
-        },
-    ) or pod2usage;
-
-    %cf = get_config();
-    die "$cf{master_dir}: $!\n" if not -d $cf{master_dir};
-
-    foreach my $dir (grep { -d } glob "$cf{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:
--- a/bin/zone-mk	Mon Jun 06 09:30:17 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-#!/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;
-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,
-                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
-                # do what you expect ( it still returns 0 on debian squeeze with
-                # dash as system shell even if cannot find the command in $PATH)
-                -noperldoc => system('perldoc -V >/dev/null 2>&1'),
-                -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 Zone in @ARGV ein verzeichnis in $master_dir an.
-    # schreibt aus den angegebenen templates die dateien $zonefile und $config
-    # in die entsprechenden verzeichnisse.
-    for my $utf8zone (@ARGV) {
-
-        my $zone     = idn_to_ascii($utf8zone, $CHARSET);
-        my $zonefile   = "$cf{master_dir}/$zone/$zone";
-        my $configfile = "$cf{zone_conf_dir}/$zone";
-        my $now        = time;
-
-        mkpath dirname $zonefile;
-
-        if (-f $zonefile and not $opt_force) {
-            say "skipping $utf8zone: zone file '$zonefile' exists.";
-            next;
-        }
-
-        if (-f $configfile and not $opt_force) {
-            say "skipping $utf8zone: config file '$configfile' exists.";
-            next;
-        }
-
-        say "zone $utf8zone ($zone) for $customer.";
-
-        my %vars = (
-            zone     => $zone,
-            utf8zone => $utf8zone,
-            now        => $now,
-            zonefile   => abs_path($zonefile),
-            customer   => $customer,
-            hostmaster => $cf{hostmaster},
-            primary    => $cf{primary},
-            secondary  => $cf{secondary},
-        );
-
-        $vars{hostmaster} =~ s/@/./g;
-
-        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> <zone>...
-
-=head1 DESCRIPTION
-
-B<zone-mk> creates a new DNS zone file and the config snippet. Nothing
-else (especially no DNSSEC, and no bind integration) is done.
-
-=head1 OPTIONS
-
-=over
-
-=item B<-f>|B<--force>
-
-Create 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
--- a/bin/zone-rm	Mon Jun 06 09:30:17 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-#!/usr/bin/perl 
-
-use v5.10;
-use warnings;
-use strict;
-use File::Path;
-use DNStools::Config qw(get_config);
-use Net::LibIDN qw(:all);
-use Pod::Usage;
-use Getopt::Long;
-
-my $CHARSET = "UTF-8";
-
-my %cf = get_config();
-
-my $opt_interactive = 0;
-
-MAIN: {
-
-    GetOptions(
-        "i|interactive!" => \$opt_interactive,
-        "h|help"         => sub { pod2usage(-exit => 0, -verbose => 1) },
-        "m|man"          => sub {
-            pod2usage(
-                -exit      => 0,
-                -verbose   => 2,
-                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
-                # do what you expect ( it still returns 0 on debian squeeze with
-                # dash as system shell even if cannot find the command in $PATH)
-                -noperldoc => system('perldoc -V >/dev/null 2>&1')
-            );
-        },
-      )
-      and @ARGV
-      or pod2usage;
-
-    for my $utf8zone (@ARGV) {
-        my $zone = idn_to_ascii($utf8zone, $CHARSET);
-
-        if (not(-d "$cf{master_dir}/$zone" or -f "$cf{zone_conf_dir}/$zone")) {
-            say "$utf8zone ($zone): nothing found";
-            next;
-        }
-
-        if ($opt_interactive) {
-            print "Remove $utf8zone ($zone)? [y/n]: ";
-            chomp($_ = <STDIN>);
-            next if not /^y(?:es)?$/i;
-        }
-
-        rmtree("$cf{master_dir}/$zone", "$cf{zone_conf_dir}/$zone",
-            { verbose => 1 });
-
-    }
-}
-
-__END__
-
-=head1 NAME
-
-    zone-rm - remove a zone
-
-=head1 SYNOPSIS
-
-    zone-rm [-i|--interactive] zone...
-
-    zone-rm [-h|--help]
-    zone-rm [-m|--man]
-
-=head1 DESCRIPTION
-
-This removes the specified zone(s) from the configured directories.
-
-=head1 OPTIONS
-
-The standard B<-h>, B<--help>, B<-m>, and B<--man> options are
-understood.
-
-=over
-
-=item B<-i>|B<--interactive>
-
-Ask for confirmation. (default: off)
-
-=cut
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/.perltidyrc	Mon Jun 06 12:48:00 2011 +0200
@@ -0,0 +1,1 @@
+../.perltidyrc
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/dnssec-keytool	Mon Jun 06 12:48:00 2011 +0200
@@ -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 Net::LibIDN qw(:all);
+use if $ENV{DEBUG} => "Smart::Comments";
+use DNStools::Config qw(get_config);
+
+my $ME = basename $0;
+
+sub rm_keys($@);
+sub check_zone($@);
+sub create_ksk($@);
+sub create_zsk(@);
+sub post_create($@);
+
+my $CHARSET = "UTF-8";
+my %cf;
+
+MAIN: {
+
+    %cf = get_config();
+    my $cmd;
+
+    system("command -v dnssec-keygen &>/dev/null");
+    die "$ME: command 'dnssec-keygen' not found in $ENV{PATH}\n" if $?;
+
+    GetOptions(
+        "zsk" => sub { push @$cmd => "zsk" },
+        "ksk" => sub { push @$cmd => "ksk" },
+        "rm"  => sub { push @$cmd => "rm" },
+        "check" => sub { $cmd = "check" },
+        "h|help" => sub { pod2usage(-exit => 0, -verbose => 1) },
+        "m|man"  => sub {
+            pod2usage(
+                -exit      => 0,
+                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
+                # do what you expect ( it still returns 0 on debian squeeze with
+                # dash as system shell even if cannot find the command in $PATH)
+                -noperldoc => system('perldoc -V >/dev/null 2>&1'),
+                -verbose   => 2
+            );
+        },
+      )
+      and @ARGV
+      and @$cmd == 1
+      and $cmd = $cmd->[0]
+      or pod2usage;
+
+    # checks the zones in argv if they're managed ones
+    my @zones;
+    foreach my $utf8zone (@ARGV) {
+        my $zone = idn_to_ascii($utf8zone, $CHARSET);
+
+        die "zone $zone is not managed\n"
+          if not -f "$cf{master_dir}/$zone/$zone";
+
+        push @zones, $zone;
+    }
+
+    given ($cmd) {
+        when ("zsk")   { exit create_zsk(@zones) };
+        #when ("ksk")   { return create_ksk(@zones) };
+        #when ("check") { return check_zone(@zones) };
+        #when ("rm")    { return rm_keys(@zones) };
+	default		{ die "not implemented\n" };
+    };
+}
+
+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 @zones = @_;
+
+    my $keyname;
+
+    foreach my $zone (@zones) {
+        my $dir  = "$cf{master_dir}/$zone";
+
+        chomp($keyname = `cd $dir && dnssec-keygen -a RSASHA1 -b 512 -n ZONE $zone`);
+
+	my @index;
+	open(my $idx, "+>>", "$dir/.index.zsk") or die "Can't open $dir/.index.zsk: $!\n";
+	seek($idx, 0, 0);
+	chomp(@index = <$idx>);
+
+        push @index, $keyname;
+	shift @index if @index > 2;
+
+	truncate($idx, 0);
+	print $idx join "\n" => @index, "";
+	close($idx);
+
+        say "$zone: new ZSK $keyname";
+
+        open(my $kc, ">", "$dir/.keycounter") or die "$dir/.keycounter: $!\n";
+        print $kc "0\n";
+        close($kc);
+    }
+}
+
+sub check_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 - key management
+
+=head1 SYNOPSIS
+
+    dnssec-keytool {--zsk|--ksk|--rm|--check} zone...
+
+=head1 DESCRIPTION
+
+Blabla.
+
+=head1 OPTIONS
+
+=over
+
+=item B<--zsk> 
+
+Create a new ZSK for the zones.
+
+=item B<--ksk>
+
+Create a new KSK for the zones.
+
+=item B<--rm>
+
+Remote all key material from the zones.
+
+=item B<--check>
+
+???
+
+=back
+
+=cut
+
+# vim:sts=4 sw=4 aw ai sm:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/update-serial	Mon Jun 06 12:48:00 2011 +0200
@@ -0,0 +1,126 @@
+#!/usr/bin/perl -w
+
+#    Copyright (C) 2011 Matthias Förste
+#    Copyright (C) 2010, 2011 Heiko Schlittermann
+#    Copyright (C) 2010 Andre Süß
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#    Matthias Förste <foerste@schlittermann.de>
+
+=encoding utf8
+=cut
+
+use v5.10;
+use strict;
+use warnings;
+
+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);
+use DNStools::UpdateSerial;
+
+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,
+                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
+                # do what you expect ( it still returns 0 on debian squeeze with
+                # dash as system shell even if cannot find the command in $PATH)
+                -noperldoc => system('perldoc -V >/dev/null 2>&1')
+            );
+        }
+    ) or pod2usage;
+
+    # merge the config and the defined options from commandline
+    my @configs = ( "dnstools.conf", "$ENV{HOME}/.dnstools.conf",
+        "/etc/dnstools.conf");
+    unshift @configs, $ENV{DNSTOOLS_CONF} if defined $ENV{DNSTOOLS_CONF};
+    %config = get_config @configs, \%opt;
+
+    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)) {
+#        say "XXX: candidate $zone";
+        update_serial($zone);
+        sign($zone) if dnssec_enabled($zone, "$config{master_dir}/$config{indexzone}/$config{indexzone}");
+#        say "XXX: $zone should be signed" if dnssec_enabled($zone, "$config{master_dir}/$config{indexzone}/$config{indexzone}");
+    }
+
+    file_entry;
+    mk_zone_conf($config{bind_dir}, $config{zone_conf_dir});
+    server_reload;
+
+}
+
+__END__
+
+=pod
+
+=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>
+
+TODO
+
+=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 AUTHORS
+
+Matthias Förste L<<foerste@schlittermann.de>>, Heiko Schlittermann L<<hs@schlittermann.de>>, Andre Süss 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/sbin/zone-ls	Mon Jun 06 12:48:00 2011 +0200
@@ -0,0 +1,147 @@
+#! /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 %cf;
+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,
+                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
+                # do what you expect ( it still returns 0 on debian squeeze with
+                # dash as system shell even if cannot find the command in $PATH)
+                -noperldoc => system('perldoc -V >/dev/null 2>&1')
+            );
+        },
+    ) or pod2usage;
+
+    %cf = get_config();
+    die "$cf{master_dir}: $!\n" if not -d $cf{master_dir};
+
+    foreach my $dir (grep { -d } glob "$cf{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/sbin/zone-mk	Mon Jun 06 12:48:00 2011 +0200
@@ -0,0 +1,126 @@
+#!/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;
+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,
+                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
+                # do what you expect ( it still returns 0 on debian squeeze with
+                # dash as system shell even if cannot find the command in $PATH)
+                -noperldoc => system('perldoc -V >/dev/null 2>&1'),
+                -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 Zone in @ARGV ein verzeichnis in $master_dir an.
+    # schreibt aus den angegebenen templates die dateien $zonefile und $config
+    # in die entsprechenden verzeichnisse.
+    for my $utf8zone (@ARGV) {
+
+        my $zone     = idn_to_ascii($utf8zone, $CHARSET);
+        my $zonefile   = "$cf{master_dir}/$zone/$zone";
+        my $configfile = "$cf{zone_conf_dir}/$zone";
+        my $now        = time;
+
+        mkpath dirname $zonefile;
+
+        if (-f $zonefile and not $opt_force) {
+            say "skipping $utf8zone: zone file '$zonefile' exists.";
+            next;
+        }
+
+        if (-f $configfile and not $opt_force) {
+            say "skipping $utf8zone: config file '$configfile' exists.";
+            next;
+        }
+
+        say "zone $utf8zone ($zone) for $customer.";
+
+        my %vars = (
+            zone     => $zone,
+            utf8zone => $utf8zone,
+            now        => $now,
+            zonefile   => abs_path($zonefile),
+            customer   => $customer,
+            hostmaster => $cf{hostmaster},
+            primary    => $cf{primary},
+            secondary  => $cf{secondary},
+        );
+
+        $vars{hostmaster} =~ s/@/./g;
+
+        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> <zone>...
+
+=head1 DESCRIPTION
+
+B<zone-mk> creates a new DNS zone file and the config snippet. Nothing
+else (especially no DNSSEC, and no bind integration) is done.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-f>|B<--force>
+
+Create 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/sbin/zone-rm	Mon Jun 06 12:48:00 2011 +0200
@@ -0,0 +1,85 @@
+#!/usr/bin/perl 
+
+use v5.10;
+use warnings;
+use strict;
+use File::Path;
+use DNStools::Config qw(get_config);
+use Net::LibIDN qw(:all);
+use Pod::Usage;
+use Getopt::Long;
+
+my $CHARSET = "UTF-8";
+
+my %cf = get_config();
+
+my $opt_interactive = 0;
+
+MAIN: {
+
+    GetOptions(
+        "i|interactive!" => \$opt_interactive,
+        "h|help"         => sub { pod2usage(-exit => 0, -verbose => 1) },
+        "m|man"          => sub {
+            pod2usage(
+                -exit      => 0,
+                -verbose   => 2,
+                # "system('perldoc -V &>/dev/null')" appears shorter, but may not
+                # do what you expect ( it still returns 0 on debian squeeze with
+                # dash as system shell even if cannot find the command in $PATH)
+                -noperldoc => system('perldoc -V >/dev/null 2>&1')
+            );
+        },
+      )
+      and @ARGV
+      or pod2usage;
+
+    for my $utf8zone (@ARGV) {
+        my $zone = idn_to_ascii($utf8zone, $CHARSET);
+
+        if (not(-d "$cf{master_dir}/$zone" or -f "$cf{zone_conf_dir}/$zone")) {
+            say "$utf8zone ($zone): nothing found";
+            next;
+        }
+
+        if ($opt_interactive) {
+            print "Remove $utf8zone ($zone)? [y/n]: ";
+            chomp($_ = <STDIN>);
+            next if not /^y(?:es)?$/i;
+        }
+
+        rmtree("$cf{master_dir}/$zone", "$cf{zone_conf_dir}/$zone",
+            { verbose => 1 });
+
+    }
+}
+
+__END__
+
+=head1 NAME
+
+    zone-rm - remove a zone
+
+=head1 SYNOPSIS
+
+    zone-rm [-i|--interactive] zone...
+
+    zone-rm [-h|--help]
+    zone-rm [-m|--man]
+
+=head1 DESCRIPTION
+
+This removes the specified zone(s) from the configured directories.
+
+=head1 OPTIONS
+
+The standard B<-h>, B<--help>, B<-m>, and B<--man> options are
+understood.
+
+=over
+
+=item B<-i>|B<--interactive>
+
+Ask for confirmation. (default: off)
+
+=cut