update-serial.pl
branchhs12
changeset 77 35905799cfd1
parent 76 7c48ae30987c
child 78 bb780a686eb6
--- 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
-    ## <zone>.signed-datei den zeitpunkt in $time mit dem ablaufdatum der
-    ## signatur, welcher aus der datei <zone>.signed ausgelesen wird.
-  ZONE: while (my $dir = glob "$config{master_dir}/*") {
-        my $zone = basename $dir;
-
-        next if not -e "$dir/$zone.signed";
-
-        open(my $fh, "$dir/$zone.signed")
-          or die "Can't open $dir/$zone.signed: $!\n";
-        push @r, $zone
-          if /RRSIG\s+SOA[\d ]+(\d{10})\d{4}\s+\(/ ~~ [<$fh>]
-              and $1 < $time;
-    }
-
-    return @r;
-}
-
-sub sign($) {
-
-    my $zone = shift;
-    my $dir  = "$config{master_dir}/$zone";
-
-    my $pid = fork // die "Can't fork: $!";
-
-    if ($pid == 0) {
-        chdir $dir or die "Can't chdir to $dir: $!\n";
-        exec "dnssec-signzone" => $zone;
-        die "Can't exec: $!\n";
-    }
-
-    wait == $pid or die "Child is lost: $!";
-    die "Can't sign zone!" if $?;
-
-    say " * $zone neu signiert";
-
-    open(my $fh, "+>>$dir/.keycounter")
-      or die "Can't open $dir/.keycounter for update: $!\n";
-    seek($fh, 0, 0);
-    my $kc = <$fh>;
-    truncate($fh, 0);
-    say $fh ++$kc;
-}
-
-sub update_serial($) {
-
-    my $zone = shift;
-
-    my $file = "$config{master_dir}/$zone/$zone";
-    my $in   = IO::File->new($file) or die "Can't open $file: $!\n";
-    my $out  = File::Temp->new(DIR => dirname $file)
-      or die "Can't open tmpfile: $!\n";
-    my $_ = join "" => <$in>;
-
-    my $serial;
-    s/^(\s+)(\d{10})(?=\s*;\s*serial)/$1 . ($serial = new_serial($2))/emi
-      or die "Serial number not found for replacement!";
-
-    print $out $_;
-
-    close($in);
-    close($out);
-
-    rename($out->filename => $file)
-      or die "Can't rename tmp to $file: $!\n";
-
-    $serial =~ s/\s*//g;
-    say " * $zone: serial incremented to $serial";
-
-    open(my $stamp, ">", dirname($file) . "/.stamp");
-    print $stamp time() . " " . localtime() . "\n";
-
-    say " * $zone: stamp aktualisiert";
-}
-
-sub new_serial($) {
-
-    my ($date, $cnt) = $_[0] =~ /(\d{8})(\d\d)/;
-
-    state $now = strftime("%4Y%02m%02d", localtime);
-
-    return $date eq $now
-      ? sprintf "%s%02d", $date, $cnt + 1
-      : "${now}00";
-
-}
-
-sub mk_zone_conf {
-
-    # erzeugt eine named.conf-datei aus den entsprechenden vorlagen.
-    our $bind_dir;
-    our $conf_dir;
-
-    open(TO, ">$bind_dir/named.conf.zones")
-      or die "$bind_dir/named.conf.zones: $!\n";
-    while (<$conf_dir/*>) {
-        open(FROM, "$_") or die "$_: $! \n";
-        print TO <FROM>;
-        close(FROM);
-    }
-    close(TO);
-    print "** zonekonfiguration erzeugt\n";
-}
-
-sub update_index($) {
-    my $indexzone = shift;
-
-    my @iz;
-
-    {
-        open(my $fh, "$config{master_dir}/$indexzone/$indexzone")
-          or die "$config{master_dir}/$indexzone/$indexzone: $!\n";
-        chomp(@iz = grep !/ZONE::/ => <$fh>);
-    }
-
-    for my $dir (glob "$config{master_dir}/*") {
-        my $zone = basename($dir);
-        my $info = -e ("$dir/.keycounter") ? "sec-on" : "sec-off";
-        push @iz, join "::", "\t\tIN TXT\t\t\"ZONE", $zone, $info;
-    }
-
-    {
-        my $fh = File::Temp->new(DIR => "$config{master_dir}/$indexzone")
-          or die "Can't create tmpdir: $!\n";
-        print $fh join "\n" => @iz, "";
-        rename($fh->filename => "$config{master_dir}/$indexzone/$indexzone")
-          or die "Can't rename "
-          . $fh->filename
-          . " to $config{master_dir}/$indexzone/$indexzone: $!\n";
-    }
-
-    say "** index-zone aktualisiert";
-    return $indexzone;
-}
-
-sub file_entry {
-
-    # prueft jede domain, die ein verzeichnis in $config{master_dir} hat, ob sie
-    # dnssec nutzt.
-    # passt die eintraege in $config_file falls noetig an.
-    our $conf_dir;
-
-    while (glob "$config{master_dir}/*") {
-        s#($config{master_dir}/)(.*)#$2#;
-        my $zone      = $_;
-        my $zone_file = "$config{master_dir}/$zone/$zone";
-        my $conf_file = "$conf_dir/$zone";
-        my @c_content;
-
-        unless (-f "$conf_file") {
-            die "$conf_file: $! \n";
-        }
-
-        if (-e "$config{master_dir}/$zone/.keycounter") {
-            open(FILE, "<$conf_file") or die "$conf_file: $!\n";
-            @c_content = <FILE>;
-            close(FILE);
-            for (@c_content) {
-                if (m{(.*)($zone_file)(";)}) {
-                    print
-                      " * zonekonfiguration aktualisiert ($2 ==> $2.signed)\n";
-                    $_ = "$1$2.signed$3\n";
-                }
-            }
-            open(FILE, ">$conf_file") or die "$conf_file: $!\n";
-            print FILE @c_content;
-            close(FILE);
-        }
-        else {
-            open(FILE, "<$conf_file") or die "$conf_file: $!\n";
-            @c_content = <FILE>;
-            close(FILE);
-            for (@c_content) {
-                if (m{(.*)($zone_file)\.signed(.*)}) {
-                    print
-                      " * zonekonfiguration aktualisiert ($2.signed ==> $2)\n";
-                    $_ = "$1$2$3\n";
-                }
-            }
-            open(FILE, ">$conf_file") or die "$conf_file: $!\n";
-            print FILE @c_content;
-            close(FILE);
-        }
-    }
-}
-
-sub server_reload {
-    if (`rndc reload`) { print "** reload dns-server \n" }
-}
-
-sub need_rollover() {
-
-    # gibt alle zonen mit abgelaufenen keycounter
-    my @r;
-
-    while (my $kc = glob "$config{master_dir}/*/.keycounter") {
-        my $zone = basename dirname $kc;
-        my $key;
-
-        {
-            open(my $fh, $kc) or die "$kc: $!\n";
-            chomp($key = <$fh>);
-        }
-
-        push @r, $zone if $config{key_counter_end} <= $key;
-    }
-
-    return @r;
-}
-
-sub done_rollover() {
-
-    # funktion ueberprueft ob ein keyrollover fertig ist
-    # die bedingung dafuer ist das:
-    # - eine datei .index.zsk vorhanden ist
-    # - die datei .index.zsk älter ist, als die rollover-Zeit
-    # - die datei .index.zsk ueber mehr als eine zeile gross ist
-    #   (also mehr als einen Schlüssel enthält)
-    my @r;
-    my $now = time;
-
-    while (my $dir = glob "$config{master_dir}/*") {
-        my $zone = basename $dir;
-
-        my @index = ();
-        my $index_wc;
-
-        # prueft nach der ".index.zsk"-datei und erstellt den zeitpunkt
-        # an dem das key-rollover endet.
-        # rollover is done when mtime of the .index.zsk + abl_zeit is
-        # in the past
-        next if not -e "$dir/.index.zsk";
-        next if (stat _)[9] + 3600 * $config{abl_zeit} >= $now;
-
-        # prueft die anzahl der schluessel in der .index.zsk
-        open(my $fh, "$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
-        (<$fh>);
-        push @r, $zone if $. > 1;
-    }
-
-    return @r;
-}
-
-sub begin_rollover(@) {
-    my @zones = @_;
-    my @r;
-
-    # anfang des key-rollovers
-
-    foreach my $zone (@zones) {
-
-        # erzeugt zsks
-        my $dir = "$config{master_dir}/$zone";
-        my ($keyname, @keys);
-
-        # create a new key
-        {    # need to change the direcoty, thus some more effort
-                # alternativly: $keyname = `cd $dir && dnssec-keygen ...`;
-                # would do, but is more fragile on shell meta characters
-
-            open(my $keygen, "-|") or do {
-                chdir $dir or die "Can't chdir to $dir: $!\n";
-                exec "dnssec-keygen",
-                  -a => "RSASHA1",
-                  -b => 512,
-                  -n => "ZONE",
-                  $zone;
-                die "Can't exec: $!";
-            };
-            chomp($keyname = <$keygen>);
-            close($keygen) or die "dnssec-keygen failed: $@";
-        }
-
-        open(my $fh, "+>>$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
-        seek($fh, 0, 0);
-        chomp(@keys = <$fh>);
-
-        ### @keys
-
-        push @keys, $keyname;
-        shift @keys if @keys > 2;
-
-        truncate($fh, 0) or die "truncate";
-        print $fh join "\n" => @keys;
-
-        print " * $zone: neuer ZSK $keyname erstellt\n";
-
-        open($fh, ">$dir/.keycounter") or die "$dir/.keycounter: $!\n";
-        say $fh 0;
-        close($fh);
-
-        unlink_unused_keys($zone);
-        include_keys($zone);
-        push @r, $zone;
-    }
-
-    return @r;
-}
-
-sub include_keys($) {
-
-    # die funktion fugt alle schluessel in eine zonedatei
-    my $zone = shift;
-    my $dir  = "$config{master_dir}/$zone";
-
-    my $in = IO::File->new("$dir/$zone") or die "Can't open $dir/$zone: $!\n";
-    my $out = File::Temp->new(DIR => $dir) or die "Can't open tmpfile: $!\n";
-
-    print $out grep { !/\$include\s+.*key/i } $in;
-    print $out map  { "\$INCLUDE @{[basename $_]}\n" } glob "$dir/K*key";
-
-    close $in;
-    close $out;
-    rename($out->filename => "$dir/$zone")
-      or die "Can't rename tmp to $dir/$zone: $!\n";
-
-}
-
-sub unlink_unused_keys($) {
-
-    # die funktion loescht alle schluessel die nicht in der index.zsk
-    # der uebergebenen zone stehen
-    my $zone = shift;
-
-    my @keys;
-    my $dir = "$config{master_dir}/$zone";
-
-    {
-
-        # collect the keys and cut everything except the key id
-        # we cut the basenames (w/o the .private|.key suffix)
-        open(my $zsk, "<$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
-        open(my $ksk, "<$dir/.index.ksk") or die "$dir/.index.ksk: $!\n";
-        @keys = (<$zsk>, <$ksk>);
-    }
-
-    # prueft alle schluesseldateien (ksk, zsk) ob sie in der jeweiligen
-    # indexdatei beschrieben sind. wenn nicht werden sie geloescht.
-    for my $file (glob "$dir/K*.key $dir/K*.private") {
-        unlink $file if basename($file, ".key", ".private") ~~ @keys;
-    }
-}
-
-sub end_rollover(@) {
-
-    my @zones = @_;
-    my @r;
-
-    foreach my $zone (@zones) {
-
-        my $dir = "$config{master_dir}/$zone";
-
-        open(my $fh, "+>>$dir/.index.zsk")
-          or die "Can't open $dir/.index.zsk: $!\n";
-        seek($fh, 0, 0);
-        chomp(my @keys = <$fh>);
-
-        if (@keys > 1) {
-            truncate($fh, 0);
-            say $fh $keys[-1];
-        }
-        close($fh);
-
-        unlink_unused_keys($zone);
-        include_keys($zone);
-        push @r => $zone;
-    }
-
-    return @r;
-}
-
-__END__
-
-=head1 NAME
- 
- update-serial - updates the serial numbers and re-signs the zone files
-
-=head1 SYNOPSIS
-
- update-serial [options] [zone...]
-
-=head1 DESCRIPTION
-
-B<update-serial> scans the configured directories for modified zone files. On any
-file found it increments the serial number and signs the zone, if approbiate.
-
-=head1 OPTIONS
-
-=over
-
-=item B<--sign-alert-time> I<days>
-
-=item B<--key-counter-end> I<integer>
-
-Maximum number if key usages.
-
-
-=back
-
-The common options B<-h>|B<--help>|B<-m>|B<--man> are supported.
-
-=head1 AUTHOR
-
-L<andre.suess@pipkin.cc>
-
-=cut
-
-# vim:sts=4 sw=4 aw ai sm: