update-serial.pl
branchhs12
changeset 70 bffb3f2cca90
parent 67 6adcf16d5cd6
child 71 e25fc893e203
equal deleted inserted replaced
69:fdf4df74d8c5 70:bffb3f2cca90
     7 use FindBin;
     7 use FindBin;
     8 use File::Basename;
     8 use File::Basename;
     9 use Pod::Usage;
     9 use Pod::Usage;
    10 use Getopt::Long;
    10 use Getopt::Long;
    11 use File::Temp;
    11 use File::Temp;
       
    12 use IO::File;
    12 use POSIX qw(strftime);
    13 use POSIX qw(strftime);
    13 use if $ENV{DEBUG} => "Smart::Comments";
    14 use if $ENV{DEBUG} => "Smart::Comments";
    14 
    15 
    15 sub uniq(@);
    16 sub uniq(@);
    16 sub read_conf(@);
    17 sub read_conf(@);
    17 sub zones(@);
    18 sub zones(@);
    18 sub changed_zones();
    19 sub changed_zones();
    19 sub update_index($);
    20 sub update_index($);
    20 sub sign_expired($);
    21 sub signature_expired($);
    21 sub need_rollover();
    22 sub need_rollover();
    22 sub done_rollover();
    23 sub done_rollover();
    23 sub begin_rollover(@);
    24 sub begin_rollover(@);
       
    25 sub end_rollover(@);
    24 sub unlink_unused_keys($);
    26 sub unlink_unused_keys($);
    25 
    27 sub include_keys($);
    26 sub sign_zone;
    28 sub sign($);
    27 sub update_serial;
    29 sub update_serial($);
       
    30 
    28 sub mk_zone_conf;
    31 sub mk_zone_conf;
    29 sub file_entry;
    32 sub file_entry;
    30 sub server_reload;
    33 sub server_reload;
    31 sub key_to_zonefile;
       
    32 sub end_ro;
       
    33 
    34 
    34 my %config;
    35 my %config;
    35 my %opt;
    36 my %opt;
    36 
    37 
    37 MAIN: {
    38 MAIN: {
    38 
    39 
    39     GetOptions(
    40     GetOptions(
    40         "sign-alert-time=i" => \$opt{sign_alert_time},
    41         "sign-alert-time=i" => \$opt{sign_alert_time},
    41 		"key-counter-end=i" => \$opt{key_counter_end},
    42         "key-counter-end=i" => \$opt{key_counter_end},
    42         "h|help"            => sub { pod2usage(-exit 0, -verbose => 1) },
    43         "h|help"            => sub { pod2usage(-exit 0, -verbose => 1) },
    43         "m|man"             => sub {
    44         "m|man"             => sub {
    44             pod2usage(
    45             pod2usage(
    45                 -exit 0,
    46                 -exit 0,
    46                 -verbose   => 2,
    47                 -verbose   => 2,
    53     %config = (
    54     %config = (
    54         read_conf("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf"),
    55         read_conf("$FindBin::Bin/dnstools.conf", "/etc/dnstools.conf"),
    55         map { $_ => $opt{$_} } grep { defined $opt{$_} } keys %opt
    56         map { $_ => $opt{$_} } grep { defined $opt{$_} } keys %opt
    56     );
    57     );
    57 
    58 
    58     our @new_serial;     # DO NOT USE
       
    59     our @end_ro_list;    # liste mit zonen deren key-rollover fertig ist
       
    60     our $bind_dir = $config{bind_dir};
    59     our $bind_dir = $config{bind_dir};
    61     our $conf_dir = $config{zone_conf_dir};
    60     our $conf_dir = $config{zone_conf_dir};
    62 
    61 
    63     my @candidates = @ARGV ? zones(@ARGV) : changed_zones;
    62     my @candidates = @ARGV ? zones(@ARGV) : changed_zones;
    64     push @candidates, update_index($config{indexzone});
    63     push @candidates, update_index($config{indexzone});
    65     push @candidates, sign_expired($config{sign_alert_time});
    64     push @candidates, signature_expired($config{sign_alert_time});
    66 
    65 
    67     my @need_rollover = need_rollover;
    66     my @need_rollover = need_rollover;
    68     my @done_rollover = done_rollover;
    67     my @done_rollover = done_rollover;
    69     ### @candidates
    68 
    70     ### @need_rollover
    69     push @candidates, begin_rollover(@need_rollover);
    71     ### @done_rollover
    70     push @candidates, end_rollover(@done_rollover);
    72     begin_rollover(@need_rollover);    # eine rollover-beginn-sequenz
    71 
       
    72     foreach my $zone (uniq(@candidates)) {
       
    73         update_serial($zone);
       
    74         sign($zone);
       
    75     }
       
    76     say "Need to ... file_entry, mk_zone_conf, server_reload";
    73     exit;
    77     exit;
    74 
       
    75     if (@end_ro_list) {
       
    76         end_ro;                        # eine rollover-end-squenz
       
    77     }
       
    78 
       
    79     if (@new_serial) {
       
    80 
       
    81         #--update_index;     # index zone aktuallisieren
       
    82         update_serial;                 # serial aktuallisieren
       
    83         sign_zone;                     # zone signieren
       
    84     }
       
    85 
    78 
    86     file_entry;       # bearbeitet die file-eintraege der konfigurations-datei
    79     file_entry;       # bearbeitet die file-eintraege der konfigurations-datei
    87     mk_zone_conf;     # konfiguration zusammenfuegen
    80     mk_zone_conf;     # konfiguration zusammenfuegen
    88     server_reload;    # server neu laden
    81     server_reload;    # server neu laden
    89 
    82 
   157         say " * $zone: zone file modified";
   150         say " * $zone: zone file modified";
   158     }
   151     }
   159     return @r;
   152     return @r;
   160 }
   153 }
   161 
   154 
   162 sub sign_expired($) {
   155 sub signature_expired($) {
   163     my $sign_alert_time = shift;  # the time between the end and the new signing
   156     my $sign_alert_time = shift;  # the time between the end and the new signing
   164                                   # (see external configuration)
   157                                   # (see external configuration)
   165     my @r;
   158     my @r;
   166 
   159 
   167 # erzeugt $time (die zeit ab der neu signiert werden soll)
   160 # erzeugt $time (die zeit ab der neu signiert werden soll)
   184     }
   177     }
   185 
   178 
   186     return @r;
   179     return @r;
   187 }
   180 }
   188 
   181 
   189 sub sign_zone {
   182 sub sign($) {
   190 
   183 
   191     # signiert die zonen und erhoeht den wert in der keycounter-datei
   184     my $zone = shift;
   192     our @new_serial;
   185     my $dir  = "$config{master_dir}/$zone";
   193     my $zone;
   186 
   194     my $kc;
   187     my $pid = fork // die "Can't fork: $!";
   195 
   188 
   196     for (uniq(@new_serial)) {
   189     if ($pid == 0) {
   197         $zone = $_;
   190         chdir $dir or die "Can't chdir to $dir: $!\n";
   198 
   191         exec "dnssec-signzone" => $zone;
   199         unless (-e "$config{master_dir}/$zone/.index.zsk") {
   192         die "Can't exec: $!\n";
   200             next;
   193     }
   201         }
   194 
   202 
   195     wait == $pid or die "Child is lost: $!";
   203         chdir "$config{master_dir}/$zone";
   196     die "Can't sign zone!" if $?;
   204         if (`dnssec-signzone $zone 2>/dev/null`) {
   197 
   205             print " * $zone neu signiert \n";
   198     say " * $zone neu signiert";
   206 
   199 
   207             # erhoeht den keycounter
   200     open(my $fh, "+>>$dir/.keycounter")
   208             if ("$config{master_dir}/$zone/.keycounter") {
   201       or die "Can't open $dir/.keycounter for update: $!\n";
   209                 open(KC, "$config{master_dir}/$zone/.keycounter");
   202     seek($fh, 0, 0);
   210                 $kc = <KC>;
   203     my $kc = <$fh>;
   211                 close(KC);
   204     truncate($fh, 0);
   212                 $kc += 1;
   205     say $fh ++$kc;
   213             }
   206 }
   214             else {
   207 
   215                 $kc = 1;
   208 sub update_serial($) {
   216             }
   209 
   217             open(KC, ">$config{master_dir}/$zone/.keycounter");
   210     my $zone = shift;
   218             print KC $kc;
   211 
   219             close(KC);
   212     my $file = "$config{master_dir}/$zone/$zone";
   220         }
   213     my $in   = IO::File->new($file) or die "Can't open $file: $!\n";
   221         else { print "$zone konnte nicht signiert werden \n"; }
   214     my $out  = File::Temp->new(DIR => dirname $file)
   222     }
   215       or die "Can't open tmpfile: $!\n";
   223 }
   216     my $_ = join "" => <$in>;
   224 
   217 
   225 sub update_serial {
       
   226     our @new_serial;
       
   227     chomp(my $date = `date +%Y%m%d`);
       
   228     my @new_content;
       
   229     my $sdate;
       
   230     my $scount;
       
   231     my $serial;
   218     my $serial;
   232 
   219     s/^(\s+)(\d{10})(?=\s*;\s*serial)/$1 . ($serial = new_serial($2))/emi
   233     for (uniq(@new_serial)) {
   220       or die "Serial number not found for replacement!";
   234 
   221 
   235         # erhoeht den serial
   222     print $out $_;
   236         my $zone        = $_;
   223 
   237         my $file        = "$config{master_dir}/$zone/$zone";
   224     close($in);
   238         my @new_content = ();
   225     close($out);
   239 
   226 
   240         open(SER, "<$file") or die "$file: $!\n";
   227     rename($out->filename => $file)
   241         for (<SER>) {
   228       or die "Can't rename tmp to $file: $!\n";
   242             if (/^\s+(\d+)(\d{2})\s*;\s*serial/i) {
   229 
   243                 $sdate  = $1;
   230     $serial =~ s/\s*//g;
   244                 $scount = $2;
   231     say " * $zone: serial incremented to $serial";
   245                 $serial = "$sdate$scount";
   232 
   246                 if ($date eq $sdate) {
   233     open(my $stamp, ">", dirname($file) . "/.stamp");
   247                     $scount++;
   234     print $stamp time() . " " . localtime() . "\n";
   248                 }
   235 
   249                 else {
   236     say " * $zone: stamp aktualisiert";
   250                     $sdate  = $date;
   237 }
   251                     $scount = "00";
   238 
   252                 }
   239 sub new_serial($) {
   253             }
   240 
   254             if ($serial) {
   241     my ($date, $cnt) = $_[0] =~ /(\d{8})(\d\d)/;
   255                 s/$serial/$sdate$scount/;
   242 
   256             }
   243     state $now = strftime("%4Y%02m%02d", localtime);
   257             push @new_content, $_;
   244 
   258         }
   245     return $date eq $now
   259         close(SER);
   246       ? sprintf "%s%02d", $date, $cnt + 1
   260 
   247       : "${now}00";
   261         open(RES, ">$file") or die "$file: $!\n";
   248 
   262         print RES @new_content;
       
   263         close(RES);
       
   264         print " * $zone: serial erhoeht \n";
       
   265 
       
   266         open(STAMP, ">$config{master_dir}/$zone/.stamp")
       
   267           or die "$config{master_dir}/$zone/.stamp: $!\n";
       
   268         close(STAMP);
       
   269         print " * $zone: stamp aktualisiert \n";
       
   270     }
       
   271 }
   249 }
   272 
   250 
   273 sub mk_zone_conf {
   251 sub mk_zone_conf {
   274 
   252 
   275     # erzeugt eine named.conf-datei aus den entsprechenden vorlagen.
   253     # erzeugt eine named.conf-datei aus den entsprechenden vorlagen.
   430     my @zones = @_;
   408     my @zones = @_;
   431     my @r;
   409     my @r;
   432 
   410 
   433     # anfang des key-rollovers
   411     # anfang des key-rollovers
   434 
   412 
   435     #??  for (uniq(@begin_ro_list)) {
       
   436     foreach my $zone (@zones) {
   413     foreach my $zone (@zones) {
   437 
   414 
   438         # erzeugt zsks
   415         # erzeugt zsks
   439         my $dir = "$config{master_dir}/$zone";
   416         my $dir = "$config{master_dir}/$zone";
   440         my ($keyname, @keys);
   417         my ($keyname, @keys);
   441 
   418 
       
   419         # create a new key
   442         {    # need to change the direcoty, thus some more effort
   420         {    # need to change the direcoty, thus some more effort
   443                 # alternativly: $keyname = `cd $dir && dnssec-keygen ...`;
   421                 # alternativly: $keyname = `cd $dir && dnssec-keygen ...`;
   444                 # would do, but is more fragile on shell meta characters
   422                 # would do, but is more fragile on shell meta characters
   445 
   423 
   446             open(my $keygen, "-|") or do {
   424             open(my $keygen, "-|") or do {
   447                 chdir $dir or die "Can't chdir to $dir: $!\n";
   425                 chdir $dir or die "Can't chdir to $dir: $!\n";
   448                 exec "dnssec-keygen",
   426                 exec "dnssec-keygen",
   449                   -a => "RSASHA1",
   427                   -a => "RSASHA1",
   450                   -b => 512,
   428                   -b => 512,
   451                   -n => "ZONE", 
   429                   -n => "ZONE",
   452 		  $zone;
   430                   $zone;
   453                 die "Can't exec: $!";
   431                 die "Can't exec: $!";
   454             };
   432             };
   455             chomp($keyname = <$keygen>);
   433             chomp($keyname = <$keygen>);
   456             close($keygen) or die "dnssec-keygen failed: $@";
   434             close($keygen) or die "dnssec-keygen failed: $@";
   457         }
   435         }
   458 
   436 
   459         open(my $fh, "+<$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
   437         open(my $fh, "+>>$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
       
   438         seek($fh, 0, 0);
   460         chomp(@keys = <$fh>);
   439         chomp(@keys = <$fh>);
   461 
   440 
   462 	### @keys
   441         ### @keys
   463 
   442 
   464         push @keys, $keyname;
   443         push @keys, $keyname;
   465         shift @keys if @keys > 2;
   444         shift @keys if @keys > 2;
   466 
   445 
   467         seek($fh, 0, 0) or die "seek";    # FIXME
   446         truncate($fh, 0) or die "truncate";
   468         truncate($fh, 0) or die "truncate";    # FIXME
       
   469         print $fh join "\n" => @keys;
   447         print $fh join "\n" => @keys;
   470 
   448 
   471         print " * $zone: neuer ZSK $keyname erstellt\n";
   449         print " * $zone: neuer ZSK $keyname erstellt\n";
   472 
   450 
   473         open($fh, ">$dir/.keycounter") or die "$dir/.keycounter: $!\n";
   451         open($fh, ">$dir/.keycounter") or die "$dir/.keycounter: $!\n";
   474         say $fh 0;
   452         say $fh 0;
   475         close($fh);
   453         close($fh);
   476 
   454 
   477         unlink_unused_keys($zone);
   455         unlink_unused_keys($zone);
   478         &key_to_zonefile($zone);
   456         include_keys($zone);
   479         push @r, $zone;
   457         push @r, $zone;
   480     }
   458     }
   481 
   459 
   482     return @r;
   460     return @r;
   483 }
   461 }
   484 
   462 
   485 sub key_to_zonefile {
   463 sub include_keys($) {
   486 
   464 
   487     # die funktion fugt alle schluessel in eine zonedatei
   465     # die funktion fugt alle schluessel in eine zonedatei
   488     my $zone = $_[0];
   466     my $zone = shift;
   489     my $zpf  = "$config{master_dir}/$zone";
   467     my $dir  = "$config{master_dir}/$zone";
   490     my @old_content;
   468 
   491     my @new_content = ();
   469     my $in = IO::File->new("$dir/$zone") or die "Can't open $dir/$zone: $!\n";
   492 
   470     my $out = File::Temp->new(DIR => $dir) or die "Can't open tmpfile: $!\n";
   493     open(ZONEFILE, "<$zpf/$zone");
   471 
   494     @old_content = <ZONEFILE>;
   472     print $out grep { !/\$include\s+.*key/i } $in;
   495     close(ZONEFILE);
   473     print $out map  { "\$INCLUDE @{[basename $_]}\n" } glob "$dir/K*key";
   496 
   474 
   497     for (@old_content) {
   475     close $in;
   498         unless (m#INCLUDE.*key#) { push @new_content, $_; }
   476     close $out;
   499     }
   477     rename($out->filename => "$dir/$zone")
   500 
   478       or die "Can't rename tmp to $dir/$zone: $!\n";
   501     for (<$zpf/*>) {
   479 
   502         if (m#(.*\/)(K.*\.key)#) {
       
   503             push @new_content, "\$INCLUDE \"$2\"\n";
       
   504         }
       
   505     }
       
   506     open(ZONEFILE, ">$zpf/$zone") or die "$zpf/$zone: $!\n";
       
   507     print ZONEFILE @new_content;
       
   508     close(ZONEFILE);
       
   509 }
   480 }
   510 
   481 
   511 sub unlink_unused_keys($) {
   482 sub unlink_unused_keys($) {
   512 
   483 
   513     # die funktion loescht alle schluessel die nicht in der index.zsk
   484     # die funktion loescht alle schluessel die nicht in der index.zsk
   514     # der uebergebenen zone stehen
   485     # der uebergebenen zone stehen
   515     my $zone = shift;
   486     my $zone = shift;
   516 
   487 
   517     my @keys;
   488     my @keys;
   518     my $dir  = "$config{master_dir}/$zone";
   489     my $dir = "$config{master_dir}/$zone";
   519 
   490 
   520     {
   491     {
   521 	# collect the keys and cut everything except the key id
   492 
   522 	# we cut the basenames (w/o the .private|.key suffix)
   493         # collect the keys and cut everything except the key id
       
   494         # we cut the basenames (w/o the .private|.key suffix)
   523         open(my $zsk, "<$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
   495         open(my $zsk, "<$dir/.index.zsk") or die "$dir/.index.zsk: $!\n";
   524         open(my $ksk, "<$dir/.index.ksk") or die "$dir/.index.ksk: $!\n";
   496         open(my $ksk, "<$dir/.index.ksk") or die "$dir/.index.ksk: $!\n";
   525 	@keys = (<$zsk>, <$ksk>);
   497         @keys = (<$zsk>, <$ksk>);
   526     }
   498     }
   527 
   499 
   528     # prueft alle schluesseldateien (ksk, zsk) ob sie in der jeweiligen
   500     # prueft alle schluesseldateien (ksk, zsk) ob sie in der jeweiligen
   529     # indexdatei beschrieben sind. wenn nicht werden sie geloescht.
   501     # indexdatei beschrieben sind. wenn nicht werden sie geloescht.
   530     for my $file (glob "$dir/K*.key $dir/K*.private") {
   502     for my $file (glob "$dir/K*.key $dir/K*.private") {
   531 	unlink $file if basename($file, ".key", ".private") ~~ @keys;
   503         unlink $file if basename($file, ".key", ".private") ~~ @keys;
   532     }
   504     }
   533 }
   505 }
   534 
   506 
   535 sub end_ro {
   507 sub end_rollover(@) {
   536     our @end_ro_list;
   508 
   537     our @new_serial;
   509     my @zones = @_;
   538     my @content;
   510     my @r;
   539 
   511 
   540     for (@end_ro_list) {
   512     foreach my $zone (@zones) {
   541         my $zone  = $_;
   513 
   542         my $count = 0;
   514         my $dir = "$config{master_dir}/$zone";
   543         my @content;
   515 
   544         my $last_key;
   516         open(my $fh, "+>>$dir/.index.zsk")
   545 
   517           or die "Can't open $dir/.index.zsk: $!\n";
   546         open(INDEX, "<$config{master_dir}/$zone/.index.zsk");
   518         seek($fh, 0, 0);
   547         @content = <INDEX>;
   519         chomp(my @keys = <$fh>);
   548         close(INDEX);
   520 
   549 
   521         if (@keys > 1) {
   550         for (@content) {
   522             truncate($fh, 0);
   551             $count++;
   523             say $fh $keys[-1];
   552             $last_key = $_;
   524         }
   553         }
   525         close($fh);
   554         if ($count > 1) {
   526 
   555             open(INDEX, ">$config{master_dir}/$zone/.index.zsk");
       
   556             print INDEX $last_key;
       
   557             close(INDEX);
       
   558         }
       
   559         unlink_unused_keys($zone);
   527         unlink_unused_keys($zone);
   560         &key_to_zonefile($zone);
   528         include_keys($zone);
   561         push @new_serial, $zone;
   529         push @r => $zone;
   562     }
   530     }
       
   531 
       
   532     return @r;
   563 }
   533 }
   564 
   534 
   565 __END__
   535 __END__
   566 
   536 
   567 =head1 NAME
   537 =head1 NAME