bin/update-serial
changeset 116 0e966f19a616
parent 114 d7dad17fbc1b
child 117 3b9250983996
equal deleted inserted replaced
115:743fd7ffb7c3 116:0e966f19a616
    32 use File::Temp;
    32 use File::Temp;
    33 use IO::File;
    33 use IO::File;
    34 use POSIX qw(strftime);
    34 use POSIX qw(strftime);
    35 use if $ENV{DEBUG} => "Smart::Comments";
    35 use if $ENV{DEBUG} => "Smart::Comments";
    36 use DNStools::Config qw(get_config);
    36 use DNStools::Config qw(get_config);
       
    37 use DNS::ZoneParse;
    37 
    38 
    38 sub uniq(@);
    39 sub uniq(@);
    39 sub zones(@);
    40 sub zones(@);
    40 sub changed_zones();
    41 sub changed_zones();
    41 sub update_index($);
    42 sub update_index($);
    47 sub unlink_unused_keys($);
    48 sub unlink_unused_keys($);
    48 sub include_keys($);
    49 sub include_keys($);
    49 sub sign($);
    50 sub sign($);
    50 sub update_serial($);
    51 sub update_serial($);
    51 
    52 
    52 sub mk_zone_conf;
    53 sub mk_zone_conf($$);
    53 sub file_entry;
    54 sub file_entry;
    54 sub server_reload;
    55 sub server_reload;
       
    56 
       
    57 sub dnssec_enabled($$);
    55 
    58 
    56 my %config;
    59 my %config;
    57 my %opt;
    60 my %opt;
    58 
    61 
    59 MAIN: {
    62 MAIN: {
    73             );
    76             );
    74         }
    77         }
    75     ) or pod2usage;
    78     ) or pod2usage;
    76 
    79 
    77     # merge the config and the defined options from commandline
    80     # merge the config and the defined options from commandline
    78     %config = get_config("$ENV{DNSTOOLS_CONF}", "dnstools.conf",
    81     my @configs = ( "dnstools.conf", "$ENV{HOME}/.dnstools.conf",
    79         "$ENV{HOME}/.dnstools.conf", "/etc/dnstools.conf", \%opt);
    82         "/etc/dnstools.conf");
    80 
    83     unshift @configs, $ENV{DNSTOOLS_CONF} if defined $ENV{DNSTOOLS_CONF};
    81     our $bind_dir = $config{bind_dir};
    84     %config = get_config(@configs, \%opt);
    82     our $conf_dir = $config{zone_conf_dir};
       
    83 
    85 
    84     my @candidates = @ARGV ? zones(@ARGV) : changed_zones;
    86     my @candidates = @ARGV ? zones(@ARGV) : changed_zones;
    85     push @candidates, update_index($config{indexzone});
    87     push @candidates, update_index($config{indexzone});
    86     push @candidates, signature_expired($config{sign_alert_time});
    88     push @candidates, signature_expired($config{sign_alert_time});
    87 
    89 
    90 
    92 
    91     push @candidates, begin_rollover(@need_rollover);
    93     push @candidates, begin_rollover(@need_rollover);
    92     push @candidates, end_rollover(@done_rollover);
    94     push @candidates, end_rollover(@done_rollover);
    93 
    95 
    94     foreach my $zone (uniq(@candidates)) {
    96     foreach my $zone (uniq(@candidates)) {
       
    97 #        say "XXX: candidate $zone";
    95         update_serial($zone);
    98         update_serial($zone);
    96         sign($zone);
    99         sign($zone) if dnssec_enabled($zone, "$config{master_dir}/$config{indexzone}/$config{indexzone}");
    97     }
   100 #        say "XXX: $zone should be signed" if dnssec_enabled($zone, "$config{master_dir}/$config{indexzone}/$config{indexzone}");
    98     say "Need to ... file_entry, mk_zone_conf, server_reload";
   101     }
    99     exit;
   102 
   100 
   103     file_entry;
   101     file_entry;       # bearbeitet die file-eintraege der konfigurations-datei
   104     mk_zone_conf($config{bind_dir}, $config{zone_conf_dir});
   102     mk_zone_conf;     # konfiguration zusammenfuegen
   105     server_reload;
   103     server_reload;    # server neu laden
       
   104 
   106 
   105 }
   107 }
   106 
   108 
   107 sub uniq(@) {
   109 sub uniq(@) {
   108 
   110 
   141             say " * $zone: no .stamp file found";    # NOCH IN NEW_SERIAL PUSHEN
   143             say " * $zone: no .stamp file found";    # NOCH IN NEW_SERIAL PUSHEN
   142             push @r, $zone;
   144             push @r, $zone;
   143             next;
   145             next;
   144         }
   146         }
   145 
   147 
   146         my $stamp_age = -M _;
   148         my $stamp_mtime = (stat _)[8];
   147         my $file_age  = -M "$_/$zone";
   149         my $stamp_mtime2 = (stat "$_/.stamp")[8];
   148 
   150         my $zone_file_mtime  = (stat "$_/$zone")[8] or die "Can't stat '$_/$zone': $!";
   149         next if $stamp_age <= $file_age;             # should be only <
   151         # TODO: do this here?
       
   152         my $kc_file_mtime = 0;
       
   153         $kc_file_mtime = (stat "$_/.keycounter")[8] or die "Can't stat '$_/.keycounter': $!" if -f "$_/.keycounter";
       
   154 #        say "XXX: zone: $zone | stamp_mtime: $stamp_mtime| stamp_mtime2: $stamp_mtime2 | zone_file_mtime: $zone_file_mtime | kc_file_mtime: $kc_file_mtime";
       
   155 
       
   156         next unless $stamp_mtime < $zone_file_mtime or $stamp_mtime < $kc_file_mtime;
   150 
   157 
   151         push @r, $zone;
   158         push @r, $zone;
   152         say " * $zone: zone file modified";
   159         say " * $zone: zone file modified";
   153     }
   160     }
   154     return @r;
   161     return @r;
   208 }
   215 }
   209 
   216 
   210 sub update_serial($) {
   217 sub update_serial($) {
   211 
   218 
   212     my $zone = shift;
   219     my $zone = shift;
       
   220 #    say "XXX: $zone: updating serial number";
   213 
   221 
   214     my $file = "$config{master_dir}/$zone/$zone";
   222     my $file = "$config{master_dir}/$zone/$zone";
   215     my $in   = IO::File->new($file) or die "Can't open $file: $!\n";
   223     my $in   = IO::File->new($file) or die "Can't open $file: $!\n";
   216     my $out  = File::Temp->new(DIR => dirname $file)
   224     my $out  = File::Temp->new(DIR => dirname $file)
   217       or die "Can't open tmpfile: $!\n";
   225       or die "Can't open tmpfile: $!\n";
   230       or die "Can't rename tmp to $file: $!\n";
   238       or die "Can't rename tmp to $file: $!\n";
   231 
   239 
   232     $serial =~ s/\s*//g;
   240     $serial =~ s/\s*//g;
   233     say " * $zone: serial incremented to $serial";
   241     say " * $zone: serial incremented to $serial";
   234 
   242 
   235     open(my $stamp, ">", dirname($file) . "/.stamp");
   243     my ($atime, $utime) = (time) x 2; 
   236     print $stamp time() . " " . localtime() . "\n";
   244     my $s = (dirname $file) . '/.stamp';
       
   245     utime $atime, $utime, $s or die "Can't 'utime $atime, $utime, $s': $!";
   237 
   246 
   238     say " * $zone: stamp aktualisiert";
   247     say " * $zone: stamp aktualisiert";
       
   248 #    say " XXX $zone: stamp '$s' aktualisiert";
   239 }
   249 }
   240 
   250 
   241 sub new_serial($) {
   251 sub new_serial($) {
   242 
   252 
   243     my ($date, $cnt) = $_[0] =~ /(\d{8})(\d\d)/;
   253     my ($date, $cnt) = $_[0] =~ /(\d{8})(\d\d)/;
   248       ? sprintf "%s%02d", $date, $cnt + 1
   258       ? sprintf "%s%02d", $date, $cnt + 1
   249       : "${now}00";
   259       : "${now}00";
   250 
   260 
   251 }
   261 }
   252 
   262 
   253 sub mk_zone_conf {
   263 sub mk_zone_conf($$) {
   254 
   264 
   255     # erzeugt eine named.conf-datei aus den entsprechenden vorlagen.
   265     # erzeugt eine named.conf-datei aus den entsprechenden vorlagen.
   256     our $bind_dir;
   266     my ($bind_dir, $conf_dir) = @_;
   257     our $conf_dir;
       
   258 
   267 
   259     open(TO, ">$bind_dir/named.conf.zones")
   268     open(TO, ">$bind_dir/named.conf.zones")
   260       or die "$bind_dir/named.conf.zones: $!\n";
   269       or die "$bind_dir/named.conf.zones: $!\n";
   261     while (<$conf_dir/*>) {
   270     while (<$conf_dir/*>) {
   262         open(FROM, "$_") or die "$_: $! \n";
   271         open(FROM, "$_") or die "$_: $! \n";
   266     close(TO);
   275     close(TO);
   267     print "** zonekonfiguration erzeugt\n";
   276     print "** zonekonfiguration erzeugt\n";
   268 }
   277 }
   269 
   278 
   270 sub update_index($) {
   279 sub update_index($) {
       
   280 
   271     my $indexzone = shift;
   281     my $indexzone = shift;
   272 
   282 
   273     my @iz;
   283     my @iz;
   274 
   284 
   275     {
   285     {
   279     }
   289     }
   280 
   290 
   281     for my $dir (glob "$config{master_dir}/*") {
   291     for my $dir (glob "$config{master_dir}/*") {
   282         my $zone = basename($dir);
   292         my $zone = basename($dir);
   283         my $info = -e ("$dir/.keycounter") ? "sec-on" : "sec-off";
   293         my $info = -e ("$dir/.keycounter") ? "sec-on" : "sec-off";
   284         push @iz, join "::", "\t\tIN TXT\t\t\"ZONE", $zone, $info;
   294         push @iz, join "::", "\t\tIN TXT\t\t\"ZONE", $zone, $info . '"';
   285     }
   295     }
   286 
   296 
   287     {
   297     {
   288         my $fh = File::Temp->new(DIR => "$config{master_dir}/$indexzone")
   298         my $fh = File::Temp->new(DIR => "$config{master_dir}/$indexzone")
   289           or die "Can't create tmpdir: $!\n";
   299           or die "Can't create tmpdir: $!\n";
   290         print $fh join "\n" => @iz, "";
   300         print $fh join "\n" => @iz, "";
   291         rename($fh->filename => "$config{master_dir}/$indexzone/$indexzone")
   301         rename($fh->filename => "$config{master_dir}/$indexzone/$indexzone")
   292           or die "Can't rename "
   302           or die "Can't rename "
   293           . $fh->filename
   303           . $fh->filename
   294           . " to $config{master_dir}/$indexzone/$indexzone: $!\n";
   304           . " to $config{master_dir}/$indexzone/$indexzone: $!\n";
       
   305         $fh->unlink_on_destroy(0);
   295     }
   306     }
   296 
   307 
   297     say "** index-zone aktualisiert";
   308     say "** index-zone aktualisiert";
   298     return $indexzone;
   309     return $indexzone;
   299 }
   310 }
   301 sub file_entry {
   312 sub file_entry {
   302 
   313 
   303     # prueft jede domain, die ein verzeichnis in $config{master_dir} hat, ob sie
   314     # prueft jede domain, die ein verzeichnis in $config{master_dir} hat, ob sie
   304     # dnssec nutzt.
   315     # dnssec nutzt.
   305     # passt die eintraege in $config_file falls noetig an.
   316     # passt die eintraege in $config_file falls noetig an.
   306     our $conf_dir;
   317     my $cd = $config{zone_conf_dir};
   307 
   318     my $md = $config{master_dir};
   308     while (glob "$config{master_dir}/*") {
   319 
   309         s#($config{master_dir}/)(.*)#$2#;
   320     while (glob "$md/*") {
   310         my $zone      = $_;
   321         m#($md/)(.*)#;
   311         my $zone_file = "$config{master_dir}/$zone/$zone";
   322         my $z  = $2;
   312         my $conf_file = "$conf_dir/$zone";
   323         my $cf = "$cd/$z";
   313         my @c_content;
   324         my $de = dnssec_enabled $z, "$md/$config{indexzone}/$config{indexzone}";
   314 
   325         my $suf = $de ? '.signed' : '';
   315         unless (-f "$conf_file") {
   326         # TODO: assuming that paths in $md and in zone config snippets match somehow
   316             die "$conf_file: $! \n";
   327         my $zp = "$z/$z$suf";
       
   328         my $zf = "$md/$z/$z$suf";
       
   329 
       
   330         my ($files, $changed) = (0, 0);
       
   331         my $czf;
       
   332         open C, "+<$cf" or die "Cant't open '$cf': $!";
       
   333         my @lines = <C>; # TODO: deal with race condition?
       
   334         my ($mode, $uid, $gid, $atime, $mtime) = (stat C)[2, 4, 5, 8, 9] or die "Can't stat: $!";
       
   335         $mode &= 07777;
       
   336         for (@lines) {
       
   337             next unless /^\s*file\s+"([^"]*)"\s*;\s*$/;
       
   338             $czf = $1;
       
   339             $files++;
       
   340             $_ = qq(\tfile "$zf";) and $changed++ unless $czf =~ m#\Q$z/$z$suf\E$#;
   317         }
   341         }
   318 
   342 
   319         if (-e "$config{master_dir}/$zone/.keycounter") {
   343         die "Multiple file statements found in '$cf' (maybe inside multiline comments)" if $files > 1;
   320             open(FILE, "<$conf_file") or die "$conf_file: $!\n";
   344         return unless $changed;
   321             @c_content = <FILE>;
   345 
   322             close(FILE);
   346         # file statement in config snippet doesnt match, so we make a backup first and write a new config
   323             for (@c_content) {
   347         my $cb = "$cf.bak";
   324                 if (m{(.*)($zone_file)(";)}) {
   348         open B, ">$cb" or die "Can't open '$cb': $!";
   325                     print
   349         print B @lines;
   326                       " * zonekonfiguration aktualisiert ($2 ==> $2.signed)\n";
   350         close B;
   327                     $_ = "$1$2.signed$3\n";
   351         chown $uid, $gid, $cb or die "Can't 'chown $uid, $gid, $cb': $!";
   328                 }
   352         chmod $mode, $cb or die "Can't 'chmod $mode, $cb': $!";
   329             }
   353         utime $atime, $mtime, $cb or die "Can't 'utime $atime, $mtime, $cb': $!";
   330             open(FILE, ">$conf_file") or die "$conf_file: $!\n";
   354 
   331             print FILE @c_content;
   355         seek C, 0, 0 or die "Can't seek C, 0, 0: $!";
   332             close(FILE);
   356         # write back @lines we modified earlier
   333         }
   357         print C @lines;
   334         else {
   358         close C;
   335             open(FILE, "<$conf_file") or die "$conf_file: $!\n";
   359 
   336             @c_content = <FILE>;
   360         print " * zonekonfiguration aktualisiert ($czf ==> $zf)\n";
   337             close(FILE);
   361 
   338             for (@c_content) {
   362     }
   339                 if (m{(.*)($zone_file)\.signed(.*)}) {
   363 
   340                     print
       
   341                       " * zonekonfiguration aktualisiert ($2.signed ==> $2)\n";
       
   342                     $_ = "$1$2$3\n";
       
   343                 }
       
   344             }
       
   345             open(FILE, ">$conf_file") or die "$conf_file: $!\n";
       
   346             print FILE @c_content;
       
   347             close(FILE);
       
   348         }
       
   349     }
       
   350 }
   364 }
   351 
   365 
   352 sub server_reload {
   366 sub server_reload {
   353     if (`rndc reload`) { print "** reload dns-server \n" }
   367     if (`rndc reload`) { print "** reload dns-server \n" }
   354 }
   368 }
   532     }
   546     }
   533 
   547 
   534     return @r;
   548     return @r;
   535 }
   549 }
   536 
   550 
       
   551 # dnssec_enabled($zone, $path_to_indexzone_file)
       
   552 # return true if the index zone indicates that dnssec is enabled for a zone
       
   553 sub dnssec_enabled($$) {
       
   554 
       
   555     my ($z, $if) = @_;
       
   556     my $re = qr/^[^;]*IN\s+TXT\s+"ZONE::\Q$z\E::sec-(on|off)"/;
       
   557     my $r;
       
   558 
       
   559     open I, "<$if" or die "Can't open index zone file '<$if': $!";
       
   560     while (<I>) {
       
   561 #        say "XXX: match: $_" if /$re/;
       
   562         $r = $1 eq 'on' and last if /$re/;
       
   563     }
       
   564     close I;
       
   565 
       
   566     return $r;
       
   567 
       
   568 }
       
   569 
   537 __END__
   570 __END__
   538 
   571 
   539 =pod
   572 =pod
   540 
   573 
   541 =head1 NAME
   574 =head1 NAME