--- 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