# HG changeset patch # User Matthias Förste # Date 1307347844 -7200 # Node ID d8fa604888680a7040b7ccb0fedc288e1a0849a0 # Parent 5578cb7933c1401b775d10f2894db0c8d6098860 install scripts to sbin diff -r 5578cb7933c1 -r d8fa60488868 Build.PL --- a/Build.PL Mon Jun 06 09:30:17 2011 +0200 +++ b/Build.PL Mon Jun 06 10:10:44 2011 +0200 @@ -36,6 +36,8 @@ "Template" => "0" }, script_files => [glob "bin/*"], # avoid .swp files + sbin_files => { map { $_ => $_ unless /.(bak|orig)$/ } glob "sbin/*" } ); +$build->add_build_element('sbin'); $build->create_build_script; diff -r 5578cb7933c1 -r d8fa60488868 bin/.perltidyrc --- 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 diff -r 5578cb7933c1 -r d8fa60488868 bin/dnssec-keytool --- 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 = ; - 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 = ; - 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 = ; - close(INDEX); - open(INDEX, "<$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n"; - push @keylist, ; - - # 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 = ; - 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: diff -r 5578cb7933c1 -r d8fa60488868 bin/update-serial --- 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 . -# -# Matthias Förste - -=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 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 - -TODO - -=item B<--key-counter-end> I - -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<>, Heiko Schlittermann L<>, Andre Süss L<> - -=cut - -# vim:sts=4 sw=4 aw ai sm: diff -r 5578cb7933c1 -r d8fa60488868 bin/zone-ls --- 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 - (?\d\d\d\d) - (?\d\d) - (?\d\d) - (?\d\d) - (?\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 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 - -=cut - -# vim:ts=4 sw=4 ai si aw: diff -r 5578cb7933c1 -r d8fa60488868 bin/zone-mk --- 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] ... - -=head1 DESCRIPTION - -B 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 is used. - -=cut diff -r 5578cb7933c1 -r d8fa60488868 bin/zone-rm --- 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($_ = ); - 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 diff -r 5578cb7933c1 -r d8fa60488868 sbin/.perltidyrc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbin/.perltidyrc Mon Jun 06 10:10:44 2011 +0200 @@ -0,0 +1,1 @@ +../.perltidyrc \ No newline at end of file diff -r 5578cb7933c1 -r d8fa60488868 sbin/dnssec-keytool --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbin/dnssec-keytool Mon Jun 06 10:10:44 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 = ; + 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 = ; + 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 = ; + close(INDEX); + open(INDEX, "<$zpf/.index.ksk") or die "$zpf/.index.ksk: $!\n"; + push @keylist, ; + + # 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 = ; + 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: diff -r 5578cb7933c1 -r d8fa60488868 sbin/update-serial --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbin/update-serial Mon Jun 06 10:10:44 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 . +# +# Matthias Förste + +=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 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 + +TODO + +=item B<--key-counter-end> I + +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<>, Heiko Schlittermann L<>, Andre Süss L<> + +=cut + +# vim:sts=4 sw=4 aw ai sm: diff -r 5578cb7933c1 -r d8fa60488868 sbin/zone-ls --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbin/zone-ls Mon Jun 06 10:10:44 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 + (?\d\d\d\d) + (?\d\d) + (?\d\d) + (?\d\d) + (?\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 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 + +=cut + +# vim:ts=4 sw=4 ai si aw: diff -r 5578cb7933c1 -r d8fa60488868 sbin/zone-mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbin/zone-mk Mon Jun 06 10:10:44 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] ... + +=head1 DESCRIPTION + +B 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 is used. + +=cut diff -r 5578cb7933c1 -r d8fa60488868 sbin/zone-rm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sbin/zone-rm Mon Jun 06 10:10:44 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($_ = ); + 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