update-serial.pl
changeset 29 49003d3e8a99
parent 28 8239b2754411
parent 26 3145999d34e3
equal deleted inserted replaced
28:8239b2754411 29:49003d3e8a99
       
     1 #! /usr/bin/perl
       
     2 # (c) 1998 Heiko Schlittermann <heiko@datom.de>
       
     3 # (c) 2010 Heiko Schlittermann <hs@schlittermann.de>
       
     4 #
       
     5 # … work in progress do integrate dnssec (branch suess)
       
     6 #
       
     7 # Update the serial numbers in zone files
       
     8 # The serial number needs to match a specified pattern (see
       
     9 # the line marked w/ PATTERN)
       
    10 #
       
    11 # Limitations:
       
    12 # - the zonefile needs to fit entirely into memory
       
    13 #
       
    14 # ToDo:
       
    15 # . test against an md5 sum, not just the date of the stamp file
       
    16 
       
    17 use strict;
       
    18 use warnings;
       
    19 use 5.010;
       
    20 
       
    21 use File::Copy;
       
    22 use File::Basename;
       
    23 use Getopt::Long;
       
    24 use Pod::Usage;
       
    25 
       
    26 #my $dnssec_sign = "../dnstools/dnssec-sign";
       
    27 my $ME      = basename $0;
       
    28 my $VERSION = '__VERSION__';
       
    29 my $URL     = "https://keller.schlittermann.de/hg/ius/update-serial";
       
    30 
       
    31 my $master_dir  = "/etc/bind/master";
       
    32 my $opt_verbose = 0;
       
    33 my $opt_reload  = 0;
       
    34 my $opt_dnssec  = 0;
       
    35 
       
    36 {
       
    37 
       
    38     # remove temporary files
       
    39     my @cleanup;
       
    40 
       
    41     sub cleanup(@) {
       
    42         return push @cleanup, @_ if @_;
       
    43         unlink @cleanup;
       
    44     }
       
    45 }
       
    46 
       
    47 sub next_serial($);
       
    48 
       
    49 END { cleanup(); }
       
    50 $SIG{INT} = sub { exit 1 };
       
    51 
       
    52 MAIN: {
       
    53 
       
    54     GetOptions(
       
    55         "v|verbose!"      => \$opt_verbose,
       
    56         "y|r|yes|reload!" => \$opt_reload,
       
    57         "dnssec!"         => \$opt_dnssec,
       
    58         "h|help"          => sub { pod2usage(-exit => 0, -verbose => 1) },
       
    59         "m|man"           => sub {
       
    60             pod2usage(
       
    61                 -noperldoc => system("perldoc -V &>/dev/null"),
       
    62                 -exit      => 0,
       
    63                 -verbose   => 2
       
    64             );
       
    65         },
       
    66         "version" => sub { print "$0 version:$VERSION from $URL\n"; exit 0; },
       
    67     ) or pod2usage();
       
    68 
       
    69     warn "DNSSEC support is currently disabled!\n"
       
    70       if not $opt_dnssec;
       
    71 
       
    72     -d $master_dir or die "directory $master_dir not found\n" if not @ARGV;
       
    73     my @files = map { (-d) ? glob("$_/*") : $_ } @ARGV ? @ARGV : $master_dir;
       
    74 
       
    75     my $changed = 0;
       
    76     foreach my $file (@files) {
       
    77 
       
    78         $file = undef, next if basename($file) !~ /[:.]/;
       
    79         $file = undef, next if $file =~ /\.bak|~$/;
       
    80 
       
    81         # zone file could be
       
    82         #	$master_dir/xxx.de
       
    83         #    or $master_dir/xxx.de/xxx.de
       
    84         $file = "$file/" . basename($file) if -d $file;
       
    85 
       
    86         my $stamp_file = dirname($file) . "/.stamp/" . basename($file);
       
    87         print "$file:" if $opt_verbose;
       
    88 
       
    89         if (stat $stamp_file and (stat _)[9] >= (stat $file)[9]) {
       
    90             print " fresh, skipping." if $opt_verbose;
       
    91             next;
       
    92         }
       
    93 
       
    94         $_ = dirname($stamp_file);
       
    95         mkdir or die "mkdir $_: $!\n" if not -d;
       
    96 
       
    97         my $now = time;
       
    98 
       
    99         open(my $in, "+<", $file) or do {
       
   100             print "??: $!" if $opt_verbose;
       
   101             next;
       
   102         };
       
   103 
       
   104         $_ = join "", <$in>;
       
   105 
       
   106         # this pattern is too complicated
       
   107         s/^(?!;)(?<pre>			# skip lines starting with comment
       
   108 			 (?:\S+)?			# label
       
   109 			 (?:\s+\d+.)?		# ttl
       
   110 			 (?:\s+in)?			# class
       
   111 			 \s+soa				# everything before the SOA
       
   112 			 \s+\S+				# ns
       
   113 			 \s+\S+				# hostmaster
       
   114 			 (?:\s*\()?
       
   115 			 \s+)
       
   116 			 (?<serial>\d{10})		# serial
       
   117 		/$+{pre} . next_serial($+{serial})/exims or next;
       
   118 
       
   119         print "$+{serial} ⇒ @{[next_serial($+{serial})]}" if $opt_verbose;
       
   120 
       
   121         copy($file => "$file~") or die("Can't copy $file -> $file~: $!\n");
       
   122         seek($in, 0, 0) or die "Can't seek in $file: $!\n";
       
   123         truncate($in, 0) or die "Can't truncate $file: $!\n";
       
   124         print $in $_;
       
   125         close($in);
       
   126 
       
   127         # touch the stamp
       
   128         open(my $out, ">$stamp_file");
       
   129         close($out);
       
   130 
       
   131         print "$file\n" if not $opt_verbose;
       
   132 
       
   133         $changed++;
       
   134     }
       
   135     continue {
       
   136         print "\n" if $opt_verbose and defined $file;
       
   137     }
       
   138 
       
   139     if ($changed) {
       
   140         my $pidfile;
       
   141 
       
   142         print
       
   143           "** Changed $changed files, the nameserver needs to be reloaded!\n";
       
   144         foreach (
       
   145             qw(/var/run/bind/run/named.pid /var/run/named.pid /etc/named.pid))
       
   146         {
       
   147             -f $_ and $pidfile = $_ and last;
       
   148         }
       
   149 
       
   150         if ($pidfile) {
       
   151             if ($opt_reload) {
       
   152                 $_ = "y";
       
   153                 print "** Nameserver will be reloaded\n";
       
   154             }
       
   155             else { print "** Reload now? [Y/n]: "; $_ = <STDIN>; }
       
   156             /^y|^$/i and system "rndc reload";
       
   157         }
       
   158         else {
       
   159             print
       
   160               "** No PID of a running named found.  Please reload manually.\n";
       
   161         }
       
   162 
       
   163     }
       
   164 }
       
   165 
       
   166 {
       
   167     my $date;
       
   168 
       
   169     sub next_serial($) {
       
   170         if (not defined $date) {
       
   171             my ($dd, $mm, $yy) = (localtime)[3 .. 5];
       
   172             $date = sprintf "%04d%02d%02d" => $yy < 1900 ? $yy + 1900 : $yy,
       
   173               $mm + 1, $dd;
       
   174         }
       
   175 
       
   176         $_[0] =~ /(?<date>\d{8})(?<cnt>\d\d)/;
       
   177         return $date . sprintf("%02d", $+{cnt} + 1) if $date eq $+{date};
       
   178         return "${date}00";
       
   179     }
       
   180 }
       
   181 
       
   182 __END__
       
   183 
       
   184 =head1 NAME
       
   185 
       
   186   update-serial - update the serial numbers or dns zone files
       
   187 
       
   188 =head1 SYNOPSIS
       
   189 
       
   190   update-serial [-r] [-v] [file...]
       
   191 
       
   192   update-serial -h|--help
       
   193   update-serial -m|--man
       
   194 
       
   195 =head1 DESCRIPTION
       
   196 
       
   197 This script scans DNS (bind9 format) zone files and increments the
       
   198 serial number if the file is newer than some timestamp file. 
       
   199 
       
   200 =head1 OPTIONS
       
   201 
       
   202 =over
       
   203 
       
   204 =item B<-h>|B<--help>
       
   205 
       
   206 =item B<-m>|B<--man>
       
   207 
       
   208 Show the reference help / man page. (default: off)
       
   209 
       
   210 =item B<-r>|B<--reload>
       
   211 
       
   212 Automatically reload the bind (rndc reload) if some changes are applied.
       
   213 (default: off)
       
   214 
       
   215 =item B<-v>|B<--verbose>
       
   216 
       
   217 Be more verbose about the actions we're doing. (default: off)
       
   218 
       
   219 =back
       
   220 
       
   221 =head1 AUTHOR
       
   222 
       
   223 The latest sources might be found on
       
   224 L<https://keller.schlittermann.de/hg/ius/update-serial>
       
   225 
       
   226  Heiko Schlittermann <hs@schlittermann.de>
       
   227  Andre Suess (dnssec specific parts)
       
   228 
       
   229 
       
   230 =cut
       
   231 
       
   232 # vim:ts=4:sw=4:ai:aw: