diff -r 7c48ae30987c -r 35905799cfd1 update-serial.pl --- a/update-serial.pl Tue Jan 11 23:37:09 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,548 +0,0 @@ -#!/usr/bin/perl - -use v5.10; -use strict; -use warnings; - -use FindBin; -use File::Basename; -use Pod::Usage; -use Getopt::Long; -use File::Temp; -use IO::File; -use POSIX qw(strftime); -use if $ENV{DEBUG} => "Smart::Comments"; -use DNStools::Config qw(get_config); - -sub uniq(@); -sub zones(@); -sub changed_zones(); -sub update_index($); -sub signature_expired($); -sub need_rollover(); -sub done_rollover(); -sub begin_rollover(@); -sub end_rollover(@); -sub unlink_unused_keys($); -sub include_keys($); -sub sign($); -sub update_serial($); - -sub mk_zone_conf; -sub file_entry; -sub server_reload; - -my %config; -my %opt; - -MAIN: { - - GetOptions( - "sign-alert-time=i" => \$opt{sign_alert_time}, - "key-counter-end=i" => \$opt{key_counter_end}, - "h|help" => sub { pod2usage(-exit 0, -verbose => 1) }, - "m|man" => sub { - pod2usage( - -exit 0, - -verbose => 2, - -noperldoc => system("perldoc -v &>/dev/null") - ); - }, - ) or pod2usage; - - # merge the config and the defined options from commandline - %config = get_config("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf", \%opt); - - our $bind_dir = $config{bind_dir}; - our $conf_dir = $config{zone_conf_dir}; - - my @candidates = @ARGV ? zones(@ARGV) : changed_zones; - push @candidates, update_index($config{indexzone}); - push @candidates, signature_expired($config{sign_alert_time}); - - my @need_rollover = need_rollover; - my @done_rollover = done_rollover; - - push @candidates, begin_rollover(@need_rollover); - push @candidates, end_rollover(@done_rollover); - - foreach my $zone (uniq(@candidates)) { - update_serial($zone); - sign($zone); - } - say "Need to ... file_entry, mk_zone_conf, server_reload"; - exit; - - file_entry; # bearbeitet die file-eintraege der konfigurations-datei - mk_zone_conf; # konfiguration zusammenfuegen - server_reload; # server neu laden - -} - -sub uniq(@) { - - # remove duplicate entries - my %all; - @all{@_} = (); - keys %all; -} - -sub zones(@) { - - # check whether the zones in argv are managed zones and - # insert them into the list new_serial - - my @r; - - foreach (@_) { - chomp(my $zone = `idn --quiet "$_"`); - die "$zone is not managed\n" - if not -e "$config{master_dir}/$zone/$zone"; - push @r, $zone; - } - - return @r; -} - -sub changed_zones() { - - # find candidates in our master dir - my @r; - - while (glob "$config{master_dir}/*") { - my $zone = basename($_); - - if (not -e "$_/.stamp") { - say " * $zone: no .stamp file found"; # NOCH IN NEW_SERIAL PUSHEN - push @r, $zone; - next; - } - - my $stamp_age = -M _; - my $file_age = -M "$_/$zone"; - - next if $stamp_age <= $file_age; # should be only < - - push @r, $zone; - say " * $zone: zone file modified"; - } - return @r; -} - -sub signature_expired($) { - my $sign_alert_time = shift; # the time between the end and the new signing - # (see external configuration) - my @r; - -# erzeugt $time (die zeit ab der neu signiert werden soll) -# ... warum eigentlich nur bis zu den Stunden und nicht auch Minuten und Sekunden? - my $time = strftime("%Y%m%d%H" => localtime time + 3600 * $sign_alert_time); - - ## vergleicht fuer alle zonen im ordner $config{master_dir} mit einer - ## .signed-datei den zeitpunkt in $time mit dem ablaufdatum der - ## signatur, welcher aus der datei .signed ausgelesen wird. - ZONE: while (my $dir = glob "$config{master_dir}/*") { - my $zone = basename $dir; - - next if not -e "$dir/$zone.signed"; - - open(my $fh, "$dir/$zone.signed") - or die "Can't open $dir/$zone.signed: $!\n"; - push @r, $zone - if /RRSIG\s+SOA[\d ]+(\d{10})\d{4}\s+\(/ ~~ [<$fh>] - and $1 < $time; - } - - return @r; -} - -sub sign($) { - - my $zone = shift; - my $dir = "$config{master_dir}/$zone"; - - my $pid = fork // die "Can't fork: $!"; - - if ($pid == 0) { - chdir $dir or die "Can't chdir to $dir: $!\n"; - exec "dnssec-signzone" => $zone; - die "Can't exec: $!\n"; - } - - wait == $pid or die "Child is lost: $!"; - die "Can't sign zone!" if $?; - - say " * $zone neu signiert"; - - open(my $fh, "+>>$dir/.keycounter") - or die "Can't open $dir/.keycounter for update: $!\n"; - seek($fh, 0, 0); - my $kc = <$fh>; - truncate($fh, 0); - say $fh ++$kc; -} - -sub update_serial($) { - - my $zone = shift; - - my $file = "$config{master_dir}/$zone/$zone"; - my $in = IO::File->new($file) or die "Can't open $file: $!\n"; - my $out = File::Temp->new(DIR => dirname $file) - or die "Can't open tmpfile: $!\n"; - my $_ = join "" => <$in>; - - my $serial; - s/^(\s+)(\d{10})(?=\s*;\s*serial)/$1 . ($serial = new_serial($2))/emi - or die "Serial number not found for replacement!"; - - print $out $_; - - close($in); - close($out); - - rename($out->filename => $file) - or die "Can't rename tmp to $file: $!\n"; - - $serial =~ s/\s*//g; - say " * $zone: serial incremented to $serial"; - - open(my $stamp, ">", dirname($file) . "/.stamp"); - print $stamp time() . " " . localtime() . "\n"; - - say " * $zone: stamp aktualisiert"; -} - -sub new_serial($) { - - my ($date, $cnt) = $_[0] =~ /(\d{8})(\d\d)/; - - state $now = strftime("%4Y%02m%02d", localtime); - - return $date eq $now - ? sprintf "%s%02d", $date, $cnt + 1 - : "${now}00"; - -} - -sub mk_zone_conf { - - # erzeugt eine named.conf-datei aus den entsprechenden vorlagen. - our $bind_dir; - our $conf_dir; - - open(TO, ">$bind_dir/named.conf.zones") - or die "$bind_dir/named.conf.zones: $!\n"; - while (<$conf_dir/*>) { - open(FROM, "$_") or die "$_: $! \n"; - print TO ; - close(FROM); - } - close(TO); - print "** zonekonfiguration erzeugt\n"; -} - -sub update_index($) { - my $indexzone = shift; - - my @iz; - - { - open(my $fh, "$config{master_dir}/$indexzone/$indexzone") - or die "$config{master_dir}/$indexzone/$indexzone: $!\n"; - chomp(@iz = grep !/ZONE::/ => <$fh>); - } - - for my $dir (glob "$config{master_dir}/*") { - my $zone = basename($dir); - my $info = -e ("$dir/.keycounter") ? "sec-on" : "sec-off"; - push @iz, join "::", "\t\tIN TXT\t\t\"ZONE", $zone, $info; - } - - { - my $fh = File::Temp->new(DIR => "$config{master_dir}/$indexzone") - or die "Can't create tmpdir: $!\n"; - print $fh join "\n" => @iz, ""; - rename($fh->filename => "$config{master_dir}/$indexzone/$indexzone") - or die "Can't rename " - . $fh->filename - . " to $config{master_dir}/$indexzone/$indexzone: $!\n"; - } - - say "** index-zone aktualisiert"; - return $indexzone; -} - -sub file_entry { - - # prueft jede domain, die ein verzeichnis in $config{master_dir} hat, ob sie - # dnssec nutzt. - # passt die eintraege in $config_file falls noetig an. - our $conf_dir; - - while (glob "$config{master_dir}/*") { - s#($config{master_dir}/)(.*)#$2#; - my $zone = $_; - my $zone_file = "$config{master_dir}/$zone/$zone"; - my $conf_file = "$conf_dir/$zone"; - my @c_content; - - unless (-f "$conf_file") { - die "$conf_file: $! \n"; - } - - if (-e "$config{master_dir}/$zone/.keycounter") { - open(FILE, "<$conf_file") or die "$conf_file: $!\n"; - @c_content = ; - close(FILE); - for (@c_content) { - if (m{(.*)($zone_file)(";)}) { - print - " * zonekonfiguration aktualisiert ($2 ==> $2.signed)\n"; - $_ = "$1$2.signed$3\n"; - } - } - open(FILE, ">$conf_file") or die "$conf_file: $!\n"; - print FILE @c_content; - close(FILE); - } - else { - open(FILE, "<$conf_file") or die "$conf_file: $!\n"; - @c_content = ; - close(FILE); - for (@c_content) { - if (m{(.*)($zone_file)\.signed(.*)}) { - print - " * zonekonfiguration aktualisiert ($2.signed ==> $2)\n"; - $_ = "$1$2$3\n"; - } - } - open(FILE, ">$conf_file") or die "$conf_file: $!\n"; - print FILE @c_content; - close(FILE); - } - } -} - -sub server_reload { - if (`rndc reload`) { print "** reload dns-server \n" } -} - -sub need_rollover() { - - # gibt alle zonen mit abgelaufenen keycounter - my @r; - - while (my $kc = glob "$config{master_dir}/*/.keycounter") { - my $zone = basename dirname $kc; - my $key; - - { - open(my $fh, $kc) or die "$kc: $!\n"; - chomp($key = <$fh>); - } - - push @r, $zone if $config{key_counter_end} <= $key; - } - - return @r; -} - -sub done_rollover() { - - # funktion ueberprueft ob ein keyrollover fertig ist - # die bedingung dafuer ist das: - # - eine datei .index.zsk vorhanden ist - # - die datei .index.zsk älter ist, als die rollover-Zeit - # - die datei .index.zsk ueber mehr als eine zeile gross ist - # (also mehr als einen Schlüssel enthält) - my @r; - my $now = time; - - while (my $dir = glob "$config{master_dir}/*") { - my $zone = basename $dir; - - my @index = (); - my $index_wc; - - # prueft nach der ".index.zsk"-datei und erstellt den zeitpunkt - # an dem das key-rollover endet. - # rollover is done when mtime of the .index.zsk + abl_zeit is - # in the past - next if not -e "$dir/.index.zsk"; - next if (stat _)[9] + 3600 * $config{abl_zeit} >= $now; - - # prueft die anzahl der schluessel in der .index.zsk - open(my $fh, "$dir/.index.zsk") or die "$dir/.index.zsk: $!\n"; - (<$fh>); - push @r, $zone if $. > 1; - } - - return @r; -} - -sub begin_rollover(@) { - my @zones = @_; - my @r; - - # anfang des key-rollovers - - foreach my $zone (@zones) { - - # erzeugt zsks - my $dir = "$config{master_dir}/$zone"; - my ($keyname, @keys); - - # create a new key - { # need to change the direcoty, thus some more effort - # alternativly: $keyname = `cd $dir && dnssec-keygen ...`; - # would do, but is more fragile on shell meta characters - - open(my $keygen, "-|") or do { - chdir $dir or die "Can't chdir to $dir: $!\n"; - exec "dnssec-keygen", - -a => "RSASHA1", - -b => 512, - -n => "ZONE", - $zone; - die "Can't exec: $!"; - }; - chomp($keyname = <$keygen>); - close($keygen) or die "dnssec-keygen failed: $@"; - } - - open(my $fh, "+>>$dir/.index.zsk") or die "$dir/.index.zsk: $!\n"; - seek($fh, 0, 0); - chomp(@keys = <$fh>); - - ### @keys - - push @keys, $keyname; - shift @keys if @keys > 2; - - truncate($fh, 0) or die "truncate"; - print $fh join "\n" => @keys; - - print " * $zone: neuer ZSK $keyname erstellt\n"; - - open($fh, ">$dir/.keycounter") or die "$dir/.keycounter: $!\n"; - say $fh 0; - close($fh); - - unlink_unused_keys($zone); - include_keys($zone); - push @r, $zone; - } - - return @r; -} - -sub include_keys($) { - - # die funktion fugt alle schluessel in eine zonedatei - my $zone = shift; - my $dir = "$config{master_dir}/$zone"; - - my $in = IO::File->new("$dir/$zone") or die "Can't open $dir/$zone: $!\n"; - my $out = File::Temp->new(DIR => $dir) or die "Can't open tmpfile: $!\n"; - - print $out grep { !/\$include\s+.*key/i } $in; - print $out map { "\$INCLUDE @{[basename $_]}\n" } glob "$dir/K*key"; - - close $in; - close $out; - rename($out->filename => "$dir/$zone") - or die "Can't rename tmp to $dir/$zone: $!\n"; - -} - -sub unlink_unused_keys($) { - - # die funktion loescht alle schluessel die nicht in der index.zsk - # der uebergebenen zone stehen - my $zone = shift; - - my @keys; - my $dir = "$config{master_dir}/$zone"; - - { - - # collect the keys and cut everything except the key id - # we cut the basenames (w/o the .private|.key suffix) - open(my $zsk, "<$dir/.index.zsk") or die "$dir/.index.zsk: $!\n"; - open(my $ksk, "<$dir/.index.ksk") or die "$dir/.index.ksk: $!\n"; - @keys = (<$zsk>, <$ksk>); - } - - # prueft alle schluesseldateien (ksk, zsk) ob sie in der jeweiligen - # indexdatei beschrieben sind. wenn nicht werden sie geloescht. - for my $file (glob "$dir/K*.key $dir/K*.private") { - unlink $file if basename($file, ".key", ".private") ~~ @keys; - } -} - -sub end_rollover(@) { - - my @zones = @_; - my @r; - - foreach my $zone (@zones) { - - my $dir = "$config{master_dir}/$zone"; - - open(my $fh, "+>>$dir/.index.zsk") - or die "Can't open $dir/.index.zsk: $!\n"; - seek($fh, 0, 0); - chomp(my @keys = <$fh>); - - if (@keys > 1) { - truncate($fh, 0); - say $fh $keys[-1]; - } - close($fh); - - unlink_unused_keys($zone); - include_keys($zone); - push @r => $zone; - } - - return @r; -} - -__END__ - -=head1 NAME - - update-serial - updates the serial numbers and re-signs the zone files - -=head1 SYNOPSIS - - update-serial [options] [zone...] - -=head1 DESCRIPTION - -B 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 - -=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 AUTHOR - -L - -=cut - -# vim:sts=4 sw=4 aw ai sm: