update-serial.pl
changeset 15 d7e673f7e596
parent 13 8ade8810add5
child 16 638a74f94981
equal deleted inserted replaced
14:526aad9d2f83 15:d7e673f7e596
       
     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 
       
    20 use File::Copy;
       
    21 use File::Basename;
       
    22 use Getopt::Long;
       
    23 use Pod::Usage;
       
    24 
       
    25 #my $dnssec_sign = "../dnstools/dnssec-sign";
       
    26 my $ME = basename $0;
       
    27 
       
    28 my $master_dir = "/etc/bind/master";
       
    29 my $opt_verbose = 0;
       
    30 my $opt_reload = 0;
       
    31 my $opt_dnssec = 0;
       
    32 
       
    33 
       
    34 {
       
    35 	my @cleanup;
       
    36 	sub cleanup(@) { 
       
    37 		return push @cleanup, @_ if @_;
       
    38 		unlink @cleanup; 
       
    39 	}
       
    40 }
       
    41 
       
    42 END { cleanup(); }
       
    43 
       
    44 sub next_serial($);
       
    45 
       
    46 MAIN: {
       
    47 
       
    48 	GetOptions(
       
    49 		"verbose!" => \$opt_verbose,
       
    50 		"yes|reload!" => \$opt_reload,
       
    51 		"dnssec!" => \$opt_dnssec,
       
    52 	) or pod2usage();
       
    53 
       
    54 	warn "DNSSEC support is currently disabled!\n"
       
    55 		if not $opt_dnssec;
       
    56 
       
    57 	-d $master_dir or die "directory $master_dir not found\n" if not @ARGV;
       
    58 	my @files = map { (-d) ? glob("$_/*") : $_ } @ARGV ? @ARGV : $master_dir;
       
    59 
       
    60 	my $changed = 0;
       
    61 	foreach my $file (@files) {
       
    62 
       
    63 		$file = undef, next if basename($file) !~ /\./;
       
    64 		$file = undef, next if $file =~ /\.bak|~$/;
       
    65 
       
    66 		# zone file could be
       
    67 		#	$master_dir/xxx.de
       
    68 		#    or $master_dir/xxx.de/xxx.de
       
    69 		$file = "$file/" . basename($file) if -d $file;
       
    70 
       
    71 		my $stamp_file = dirname($file) . "/.stamp/" . basename($file);	
       
    72 		print "$file:" if $opt_verbose;
       
    73 
       
    74 		if (stat $stamp_file and (stat _)[9] >= (stat $file)[9]) {
       
    75 			print " fresh, skipping." if $opt_verbose;
       
    76 			next;
       
    77 		}
       
    78 
       
    79 		$_ = dirname($stamp_file);
       
    80 		mkdir or die "mkdir $_: $!\n" if not -d;
       
    81 
       
    82 		my $now = time;
       
    83 
       
    84 		open(my $in, "+<", $file) or do {
       
    85 			print "??: $!" if $opt_verbose;
       
    86 			next;
       
    87 		};
       
    88 
       
    89 		$_ = join "", <$in>;
       
    90 
       
    91 		# this pattern is too complicated
       
    92 		s/^(?!;)(?<pre>			# skip lines starting with comment
       
    93 			 (?:\S+)?			# label
       
    94 			 (?:\s+\d+.)?		# ttl
       
    95 			 (?:\s+in)?			# class
       
    96 			 \s+soa				# everything before the SOA
       
    97 			 \s+\S+				# ns
       
    98 			 \s+\S+				# hostmaster
       
    99 			 (?:\s*\()?
       
   100 			 \s+)
       
   101 			 (?<serial>\d{10})		# serial
       
   102 		/$+{pre} . next_serial($+{serial})/exims or next;
       
   103 
       
   104 		print "$+{serial} ⇒ @{[next_serial($+{serial})]}" if $opt_verbose;
       
   105 
       
   106 		copy($file => "$file~") or die("Can't copy $file -> $file~: $!\n");
       
   107 		seek($in, 0, 0) or die "Can't seek in $file: $!\n";
       
   108 		truncate($in, 0) or die "Can't truncate $file: $!\n";
       
   109 		print $in $_;
       
   110 
       
   111 		open(my $out, ">$stamp_file");
       
   112 		close($out);
       
   113 
       
   114 		print "$file\n" if not $opt_verbose;
       
   115 
       
   116 		$changed++;
       
   117 	} continue {
       
   118 		print "\n" if $opt_verbose and defined $file;
       
   119 	}
       
   120 
       
   121 	if ($changed) {
       
   122 		my $pidfile;
       
   123 
       
   124 		print "** Changed $changed files, the nameserver needs to be reloaded!\n";
       
   125 		foreach (qw(/var/run/bind/run/named.pid /var/run/named.pid /etc/named.pid)) { 
       
   126 			-f $_ and $pidfile = $_ and last; }
       
   127 
       
   128 		if ($pidfile) {
       
   129 			if ($opt_reload) { $_ = "y"; print "** Nameserver will be reloaded\n"; } 
       
   130 			else { print "** Reload now? [Y/n]: "; $_ = <STDIN>; }
       
   131 			/^y|^$/i and system "rndc reload";
       
   132 		} else {
       
   133 			print "** No PID of a running named found.  Please reload manually.\n";
       
   134 		}
       
   135 
       
   136 	}
       
   137 }
       
   138 
       
   139 {
       
   140 	my $date;
       
   141 sub next_serial($) {
       
   142 	if (not defined $date) {
       
   143 		my ($dd, $mm, $yy) = (localtime)[3..5];
       
   144 		$date = sprintf "%04d%02d%02d" => $yy < 1900 ? $yy + 1900 : $yy,  $mm + 1, $dd;
       
   145 	}
       
   146 
       
   147 	$_[0] =~ /(?<date>\d{8})(?<cnt>\d\d)/;
       
   148 	return $date . sprintf("%02d", $+{cnt}+1) if $date eq $+{date};
       
   149 	return "${date}00";
       
   150 }
       
   151 }
       
   152 
       
   153 __END__
       
   154 
       
   155 =head1 NAME
       
   156 
       
   157   update-serial - update the serial numbers or dns zone files
       
   158 
       
   159 =head1 SYNOPSIS
       
   160 
       
   161   update-serial [-r] [-v] [file...]
       
   162 
       
   163 =head1 DESCRIPTION
       
   164 
       
   165 This script scans DNS (bind9 format) zone files and increments the
       
   166 serial number if the file is newer than some timestamp file. 
       
   167 
       
   168 =head1 OPTIONS
       
   169 
       
   170 =over
       
   171 
       
   172 =item B<-r>|B<--reload>
       
   173 
       
   174 Automatically reload the bind (rndc reload) if some changes are applied.
       
   175 (default: off)
       
   176 
       
   177 =item B<-v>|B<--verbose>
       
   178 
       
   179 Be more verbose about the actions we're doing. (default: off)
       
   180 
       
   181 =back
       
   182 
       
   183 =head1 AUTHOR
       
   184 
       
   185  Heiko Schlittermann <hs@schlittermann.de>
       
   186  Andre Suess (dnssec specific parts)
       
   187 
       
   188 =cut
       
   189 
       
   190 # vim:ts=4:sw=4:ai:aw: