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