|
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: |