--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/update-serial.pl Tue Dec 21 15:59:05 2010 +0100
@@ -0,0 +1,584 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use FindBin;
+use File::Basename;
+
+sub del_double {
+
+ # entfernt doppelte eintraege in einer liste
+ my %all;
+ grep { $all{$_} = 0 } @_;
+ return (keys %all);
+}
+
+sub read_conf {
+
+ # liest die Konfiguration ein
+ my @configs = ("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf");
+ our %config;
+
+ for (grep { -f } @configs) {
+ open(CONFIG, $_) or die "Can't open $_: $!\n";
+ }
+ unless (seek(CONFIG, 0, 0)) {
+ die "Can't open config (searched: @configs)\n";
+ }
+ while (<CONFIG>) {
+ chomp;
+ s/#.*//;
+ s/\t//g;
+ s/\s//g;
+
+ next unless length;
+ my ($cname, $ccont) = split(/\s*=\s*/, $_, 2);
+ $config{$cname} = $ccont;
+ }
+ close(CONFIG);
+}
+
+sub add_argv {
+
+ # prueft ob zonen aus ARGV verwaltete zonen sind
+ # und fuegt sie, falls ja in die liste @new_serial ein
+ our @new_serial;
+ our $master_dir;
+ my $zone;
+
+ for (@ARGV) {
+ chomp($zone = `idn --quiet "$_"`);
+ if (-e "$master_dir/$zone/$zone") {
+ push @new_serial, $zone;
+ }
+ }
+}
+
+sub changed_zone {
+ our $master_dir;
+ our @new_serial;
+
+ for (<$master_dir/*>) {
+ my $zone = basename($_);
+
+ if (-e "$master_dir/$zone/.stamp") {
+ my $stamptime = (-M "$master_dir/$zone/.stamp");
+ my $filetime = (-M "$master_dir/$zone/$zone");
+ if ($stamptime > $filetime) {
+ push @new_serial, $zone;
+ print " * $zone: zonedatei wurde geaendert\n";
+ }
+ }
+ else {
+ print " * $zone: keine .stamp-datei gefunden\n"
+ ; # NOCH IN NEW_SERIAL PUSHEN
+ push @new_serial, $zone;
+ }
+ }
+
+}
+
+sub sign_end {
+ our $sign_alert_time; # die zeit zwischen dem ende und der neuen
+ # signierung (siehe externe konfiguration)
+ our $master_dir;
+ our @new_serial;
+
+ # erzeugt $time (die zeit ab der neu signiert werden soll)
+ chomp(my $unixtime = `date +%s`);
+ $unixtime = $unixtime + (3600 * $sign_alert_time);
+ my $time = `date -d \@$unixtime +%Y%m%d%H`;
+
+ ## vergleicht fuer alle zonen im ordner $master_dir mit einer
+ ## <zone>.signed-datei den zeitpunkt in $time mit dem ablaufdatum der
+ ## signatur, welcher aus der datei <zone>.signed ausgelesen wird.
+ for (<$master_dir/*>) {
+ s#($master_dir/)(.*)#$2#;
+ my $zone = $_;
+
+ if (-e "$master_dir/$zone/$zone.signed") {
+ open(ZONE, "$master_dir/$zone/$zone.signed");
+ my @zone_sig_content = <ZONE>;
+ close(ZONE);
+
+ for (@zone_sig_content) {
+ if (m#SOA.*[0-9]{14}#) {
+ s#.*([0-9]{10})([0-9]{4}).*#$1#;
+ if ($_ < $time) {
+ push @new_serial, $zone;
+ }
+ }
+ }
+ }
+ }
+}
+
+sub sign_zone {
+
+ # signiert die zonen und erhoeht den wert in der keycounter-datei
+ our @new_serial;
+ our $master_dir;
+ my $zone;
+ my $kc;
+
+ for (&del_double(@new_serial)) {
+ $zone = $_;
+
+ unless (-e "$master_dir/$zone/.index.zsk") {
+ next;
+ }
+
+ chdir "$master_dir/$zone";
+ if (`dnssec-signzone $zone 2>/dev/null`) {
+ print " * $zone neu signiert \n";
+
+ # erhoeht den keycounter
+ if ("$master_dir/$zone/.keycounter") {
+ open(KC, "$master_dir/$zone/.keycounter");
+ $kc = <KC>;
+ close(KC);
+ $kc += 1;
+ }
+ else {
+ $kc = 1;
+ }
+ open(KC, ">$master_dir/$zone/.keycounter");
+ print KC $kc;
+ close(KC);
+ }
+ else { print "$zone konnte nicht signiert werden \n"; }
+ }
+}
+
+sub update_serial {
+ our $master_dir;
+ our @new_serial;
+ chomp(my $date = `date +%Y%m%d`);
+ my @new_content;
+ my $sdate;
+ my $scount;
+ my $serial;
+
+ for (&del_double(@new_serial)) {
+
+ # erhoeht den serial
+ my $zone = $_;
+ my $file = "$master_dir/$zone/$zone";
+ my @new_content = ();
+
+ open(SER, "<$file") or die "$file: $!\n";
+ for (<SER>) {
+ if (/^\s+(\d+)(\d{2})\s*;\s*serial/i) {
+ $sdate = $1;
+ $scount = $2;
+ $serial = "$sdate$scount";
+ if ($date eq $sdate) {
+ $scount++;
+ }
+ else {
+ $sdate = $date;
+ $scount = "00";
+ }
+ }
+ if ($serial) {
+ s/$serial/$sdate$scount/;
+ }
+ push @new_content, $_;
+ }
+ close(SER);
+
+ open(RES, ">$file") or die "$file: $!\n";
+ print RES @new_content;
+ close(RES);
+ print " * $zone: serial erhoeht \n";
+
+ open(STAMP, ">$master_dir/$zone/.stamp")
+ or die "$master_dir/$zone/.stamp: $!\n";
+ close(STAMP);
+ print " * $zone: stamp aktualisiert \n";
+ }
+}
+
+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 {
+
+ # aktualisiert die indexzone;
+ our @new_serial;
+ our $indexzone;
+ our $master_dir;
+ my @iz_content_old;
+ my @iz_content_new;
+
+ open(INDEXZONE, "$master_dir/$indexzone/$indexzone")
+ or die "$master_dir/$indexzone/$indexzone: $!\n";
+ @iz_content_old = <INDEXZONE>;
+ close(INDEXZONE);
+
+ for (@iz_content_old) {
+ unless (m#ZONE::#) {
+ push @iz_content_new, $_;
+ }
+ }
+
+ for my $dir (glob "$master_dir/*") {
+ my $zone = basename($dir);
+ my $info_end = "::sec-off";
+
+ if (-e "$dir/.keycounter") {
+ $info_end = "::sec-on";
+ }
+
+ my $iz_line = "\t\tIN TXT\t\t\"ZONE::$zone$info_end\"\n";
+
+ push @iz_content_new, $iz_line;
+ }
+
+ open(INDEXZONE, ">$master_dir/$indexzone/$indexzone")
+ or die "$master_dir/$indexzone/$indexzone: $!\n";
+ print INDEXZONE @iz_content_new;
+ close(INDEXZONE);
+
+ # fuegt die index-zone in die liste damit der serial erhoet wird
+ push @new_serial, $indexzone;
+
+ print "** index-zone aktualisiert \n";
+}
+
+sub file_entry {
+
+ # prueft jede domain, die ein verzeichnis in $master_dir hat, ob sie
+ # dnssec nutzt.
+ # passt die eintraege in $config_file falls noetig an.
+ our $master_dir;
+ our $conf_dir;
+
+ while (<$master_dir/*>) {
+ s#($master_dir/)(.*)#$2#;
+ my $zone = $_;
+ my $zone_file = "$master_dir/$zone/$zone";
+ my $conf_file = "$conf_dir/$zone";
+ my @c_content;
+
+ unless (-f "$conf_file") {
+ die "$conf_file: $! \n";
+ }
+
+ if (-e "$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 to_begin_ro {
+
+ # gibt alle zonen mit abgelaufenen keycounter in die liste @begin_ro_list
+ our @begin_ro_list;
+ our $master_dir;
+ our $key_counter_end;
+ our @new_serial;
+ my $zone;
+
+ while (<$master_dir/*>) {
+ chomp($zone = $_);
+ my $key;
+
+ unless (-f "$zone/.keycounter") { next; }
+
+ open(KEY, "$zone/.keycounter") or die "$zone/.keycounter: $!\n";
+ $key = <KEY>;
+ close(KEY);
+
+ # vergleicht den wert aus der keycount-datei mit dem wert aus der
+ #dnstools.conf (key_counter_end)
+ if ($key_counter_end <= $key) {
+ $zone =~ s#($master_dir/)(.*)#$2#;
+ push @begin_ro_list, $zone;
+ }
+ }
+}
+
+sub to_end_ro {
+
+ # funktion ueberprueft ob ein keyrollover fertig ist
+ # die bedingung dafuer ist das:
+ # - eine datei .index.zsk vorhanden ist
+ # - die datei .index.zsk vor mehr x stunden geaendert wurde
+ # - die datei .index.zsk ueber mehr als zwei zeilen gross ist
+ our $master_dir;
+ our @end_ro_list;
+ our $ablauf_zeit;
+ chomp(my $now_time = `date +%s`);
+
+ for (<$master_dir/*>) {
+ my $zone = $_;
+ $zone =~ s#($master_dir/)(.*)#$2#;
+
+ my @index = ();
+ my $index_wc;
+ my @status;
+
+ # prueft nach der ".index.zsk"-datei und erstellt den zeitpunkt
+ # an dem das key-rollover endet. - $status[9]
+ if (-e "$master_dir/$zone/.index.zsk") {
+ @status = stat("$master_dir/$zone/.index.zsk");
+ $status[9] += (3600 * $ablauf_zeit);
+ }
+ else { next; }
+
+ # $status[9] ist der zeitpunkt an dem der key-rollover endet
+ # prueft ob das key-rollover-ende erreicht ist
+ unless ($status[9] < $now_time) { next; }
+
+ # prueft die anzahl der schluessel in der .index.zsk
+ open(INDEX, "$master_dir/$zone/.index.zsk")
+ or die "$master_dir/$zone/.index.zsk: $!\n";
+ @index = <INDEX>;
+ $index_wc = @index;
+ close(INDEX);
+ if ($index_wc > 1) { push @end_ro_list, $zone; }
+ }
+}
+
+sub begin_ro {
+
+ # anfang des key-rollovers
+ our @begin_ro_list;
+ our $master_dir;
+ our @new_serial;
+
+ for (&del_double(@begin_ro_list)) {
+
+ #erzeugt zsks
+ my $zone = $_;
+ my $zpf = "$master_dir/$zone";
+ my @index;
+
+ chdir "$zpf" or die "$zpf: $!\n";
+ my $keyname = `dnssec-keygen -a RSASHA1 -b 512 -n ZONE $zone`;
+
+ open(INDEX, ".index.zsk") or die "$zpf/.index.zsk: $!\n";
+ @index = <INDEX>;
+ close(INDEX);
+
+ push @index, $keyname;
+ if (@index > 2) { shift(@index); }
+
+ open(INDEX, ">.index.zsk") or die "$zpf/.index.zsk: $!\n";
+ print INDEX @index;
+ close(INDEX);
+
+ chomp($keyname);
+ print " * $zone: neuer ZSK $keyname erstellt\n";
+
+ open(KC, ">.keycounter") or die "$zpf/keycounter: $!\n";
+ print KC "0";
+ close(KC);
+
+ &kill_useless_keys($zone);
+ &key_to_zonefile($zone);
+ push @new_serial, $zone;
+ }
+}
+
+sub key_to_zonefile {
+
+ # die funktion fugt alle schluessel in eine zonedatei
+ our $master_dir;
+ my $zone = $_[0];
+ 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);
+}
+
+sub kill_useless_keys {
+
+ # die funktion loescht alle schluessel die nicht in der index.zsk
+ # der uebergebenen zone stehen
+ our $master_dir;
+ my $zone = $_[0];
+ 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>;
+
+ # kuerzt die schluessel-bezeichnung aus der indexdatei auf die id um sie
+ # besser vergleichen zu koennen.
+ for (@keylist) {
+ chomp;
+ s#K.*\+.*\+(.*)#$1#;
+ }
+
+ # prueft alle schluesseldateien (ksk, zsk) ob sie in der jeweiligen
+ # indexdatei beschrieben sind. wenn nicht werden sie geloescht.
+ for (`ls $master_dir/$zone/K*[key,private]`) {
+ 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: Schluessel $1 entfernt \n";
+ }
+ }
+ }
+}
+
+sub end_ro {
+ our @end_ro_list;
+ our $master_dir;
+ our @new_serial;
+ my @content;
+
+ for (@end_ro_list) {
+ my $zone = $_;
+ my $count = 0;
+ my @content;
+ my $last_key;
+
+ open(INDEX, "<$master_dir/$zone/.index.zsk");
+ @content = <INDEX>;
+ close(INDEX);
+
+ for (@content) {
+ $count++;
+ $last_key = $_;
+ }
+ if ($count > 1) {
+ open(INDEX, ">$master_dir/$zone/.index.zsk");
+ print INDEX $last_key;
+ close(INDEX);
+ }
+ &kill_useless_keys($zone);
+ &key_to_zonefile($zone);
+ push @new_serial, $zone;
+ }
+}
+
+&read_conf;
+
+our %config;
+our @new_serial; # liste fuer neuen serial
+our @begin_ro_list; # liste mit zonen deren key-rollover beginnt
+our @end_ro_list; # liste mit zonen deren key-rollover fertig ist
+our $master_dir = $config{master_dir};
+our $bind_dir = $config{bind_dir};
+our $conf_dir = $config{zone_conf_dir};
+our $sign_alert_time = $config{sign_alert_time};
+our $indexzone = $config{indexzone};
+our $key_counter_end = $config{key_counter_end};
+our $ablauf_zeit = $config{abl_zeit};
+
+&add_argv;
+&changed_zone;
+&sign_end;
+
+&to_begin_ro; # prueft nach beginnenden rollover-verfahren
+&to_end_ro; # prueft nach endenden rollover-verfahren
+
+if (@begin_ro_list) {
+ &begin_ro; # eine rollover-beginn-sequenz
+}
+
+if (@end_ro_list) {
+ &end_ro; # eine rollover-end-squenz
+}
+
+if (@new_serial) {
+ &update_index; # index zone aktuallisieren
+ &update_serial; # serial aktuallisieren
+ &sign_zone; # zone signieren
+}
+
+&file_entry; # bearbeitet die file-eintraege der konfigurations-datei
+&mk_zone_conf; # konfiguration zusammenfuegen
+&server_reload; # server neu laden
+
+
+
+__END__
+
+=pod
+
+=head1 TITLE
+
+update-serial
+
+=head1 SYNTAX
+
+update-serial
+
+=head1 BESCHREIBUNG
+
+=cut