3 # (c) 2010 Heiko Schlittermann <hs@schlittermann.de> |
3 # (c) 2010 Heiko Schlittermann <hs@schlittermann.de> |
4 # |
4 # |
5 # … work in progress do integrate dnssec (branch suess) |
5 # … work in progress do integrate dnssec (branch suess) |
6 # |
6 # |
7 # Update the serial numbers in zone files |
7 # Update the serial numbers in zone files |
8 # The serial number needs to match a specified pattern (see |
8 # The serial number needs to match a specified pattern (see |
9 # the line marked w/ PATTERN) |
9 # the line marked w/ PATTERN) |
10 # |
10 # |
11 # Limitations: |
11 # Limitations: |
12 # - the zonefile needs to fit entirely into memory |
12 # - the zonefile needs to fit entirely into memory |
13 # |
13 # |
14 # ToDo: |
14 # ToDo: |
15 # . test against an md5 sum, not just the date of the stamp file |
15 # . test against an md5 sum, not just the date of the stamp file |
16 |
16 |
17 use strict; |
17 use strict; |
18 use warnings; |
18 use warnings; |
23 use Pod::Usage; |
23 use Pod::Usage; |
24 |
24 |
25 #my $dnssec_sign = "../dnstools/dnssec-sign"; |
25 #my $dnssec_sign = "../dnstools/dnssec-sign"; |
26 my $ME = basename $0; |
26 my $ME = basename $0; |
27 |
27 |
28 my $master_dir = "/etc/bind/master"; |
28 my $master_dir = "/etc/bind/master"; |
29 my $opt_verbose = 0; |
29 my $opt_verbose = 0; |
30 my $opt_reload = 0; |
30 my $opt_reload = 0; |
31 my $opt_dnssec = 0; |
31 my $opt_dnssec = 0; |
32 |
|
33 |
32 |
34 { |
33 { |
35 my @cleanup; |
34 my @cleanup; |
36 sub cleanup(@) { |
35 |
37 return push @cleanup, @_ if @_; |
36 sub cleanup(@) { |
38 unlink @cleanup; |
37 return push @cleanup, @_ if @_; |
39 } |
38 unlink @cleanup; |
|
39 } |
40 } |
40 } |
41 |
41 |
42 END { cleanup(); } |
42 END { cleanup(); } |
43 |
43 |
44 sub next_serial($); |
44 sub next_serial($); |
45 |
45 |
46 MAIN: { |
46 MAIN: { |
47 |
47 |
48 GetOptions( |
48 GetOptions( |
49 "verbose!" => \$opt_verbose, |
49 "verbose!" => \$opt_verbose, |
50 "yes|reload!" => \$opt_reload, |
50 "yes|reload!" => \$opt_reload, |
51 "dnssec!" => \$opt_dnssec, |
51 "dnssec!" => \$opt_dnssec, |
52 ) or pod2usage(); |
52 ) or pod2usage(); |
53 |
53 |
54 warn "DNSSEC support is currently disabled!\n" |
54 warn "DNSSEC support is currently disabled!\n" |
55 if not $opt_dnssec; |
55 if not $opt_dnssec; |
56 |
56 |
57 -d $master_dir or die "directory $master_dir not found\n" if not @ARGV; |
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; |
58 my @files = map { (-d) ? glob("$_/*") : $_ } @ARGV ? @ARGV : $master_dir; |
59 |
59 |
60 my $changed = 0; |
60 my $changed = 0; |
61 foreach my $file (@files) { |
61 foreach my $file (@files) { |
62 |
62 |
63 $file = undef, next if basename($file) !~ /\./; |
63 $file = undef, next if basename($file) !~ /\./; |
64 $file = undef, next if $file =~ /\.bak|~$/; |
64 $file = undef, next if $file =~ /\.bak|~$/; |
65 |
65 |
66 # zone file could be |
66 # zone file could be |
67 # $master_dir/xxx.de |
67 # $master_dir/xxx.de |
68 # or $master_dir/xxx.de/xxx.de |
68 # or $master_dir/xxx.de/xxx.de |
69 $file = "$file/" . basename($file) if -d $file; |
69 $file = "$file/" . basename($file) if -d $file; |
70 |
70 |
71 my $stamp_file = dirname($file) . "/.stamp/" . basename($file); |
71 my $stamp_file = dirname($file) . "/.stamp/" . basename($file); |
72 print "$file:" if $opt_verbose; |
72 print "$file:" if $opt_verbose; |
73 |
73 |
74 if (stat $stamp_file and (stat _)[9] >= (stat $file)[9]) { |
74 if (stat $stamp_file and (stat _)[9] >= (stat $file)[9]) { |
75 print " fresh, skipping." if $opt_verbose; |
75 print " fresh, skipping." if $opt_verbose; |
76 next; |
76 next; |
77 } |
77 } |
78 |
78 |
79 $_ = dirname($stamp_file); |
79 $_ = dirname($stamp_file); |
80 mkdir or die "mkdir $_: $!\n" if not -d; |
80 mkdir or die "mkdir $_: $!\n" if not -d; |
81 |
81 |
82 my $now = time; |
82 my $now = time; |
83 |
83 |
84 open(my $in, "+<", $file) or do { |
84 open(my $in, "+<", $file) or do { |
85 print "??: $!" if $opt_verbose; |
85 print "??: $!" if $opt_verbose; |
86 next; |
86 next; |
87 }; |
87 }; |
88 |
88 |
89 $_ = join "", <$in>; |
89 $_ = join "", <$in>; |
90 |
90 |
91 # this pattern is too complicated |
91 # this pattern is too complicated |
92 s/^(?!;)(?<pre> # skip lines starting with comment |
92 s/^(?!;)(?<pre> # skip lines starting with comment |
93 (?:\S+)? # label |
93 (?:\S+)? # label |
94 (?:\s+\d+.)? # ttl |
94 (?:\s+\d+.)? # ttl |
95 (?:\s+in)? # class |
95 (?:\s+in)? # class |
96 \s+soa # everything before the SOA |
96 \s+soa # everything before the SOA |
97 \s+\S+ # ns |
97 \s+\S+ # ns |
99 (?:\s*\()? |
99 (?:\s*\()? |
100 \s+) |
100 \s+) |
101 (?<serial>\d{10}) # serial |
101 (?<serial>\d{10}) # serial |
102 /$+{pre} . next_serial($+{serial})/exims or next; |
102 /$+{pre} . next_serial($+{serial})/exims or next; |
103 |
103 |
104 print "$+{serial} ⇒ @{[next_serial($+{serial})]}" if $opt_verbose; |
104 print "$+{serial} ⇒ @{[next_serial($+{serial})]}" if $opt_verbose; |
105 |
105 |
106 copy($file => "$file~") or die("Can't copy $file -> $file~: $!\n"); |
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"; |
107 seek($in, 0, 0) or die "Can't seek in $file: $!\n"; |
108 truncate($in, 0) or die "Can't truncate $file: $!\n"; |
108 truncate($in, 0) or die "Can't truncate $file: $!\n"; |
109 print $in $_; |
109 print $in $_; |
110 |
110 |
111 open(my $out, ">$stamp_file"); |
111 open(my $out, ">$stamp_file"); |
112 close($out); |
112 close($out); |
113 |
113 |
114 print "$file\n" if not $opt_verbose; |
114 print "$file\n" if not $opt_verbose; |
115 |
115 |
116 $changed++; |
116 $changed++; |
117 } continue { |
117 } |
118 print "\n" if $opt_verbose and defined $file; |
118 continue { |
119 } |
119 print "\n" if $opt_verbose and defined $file; |
120 |
120 } |
121 if ($changed) { |
121 |
122 my $pidfile; |
122 if ($changed) { |
123 |
123 my $pidfile; |
124 print "** Changed $changed files, the nameserver needs to be reloaded!\n"; |
124 |
125 foreach (qw(/var/run/bind/run/named.pid /var/run/named.pid /etc/named.pid)) { |
125 print |
126 -f $_ and $pidfile = $_ and last; } |
126 "** Changed $changed files, the nameserver needs to be reloaded!\n"; |
127 |
127 foreach ( |
128 if ($pidfile) { |
128 qw(/var/run/bind/run/named.pid /var/run/named.pid /etc/named.pid)) |
129 if ($opt_reload) { $_ = "y"; print "** Nameserver will be reloaded\n"; } |
129 { |
130 else { print "** Reload now? [Y/n]: "; $_ = <STDIN>; } |
130 -f $_ and $pidfile = $_ and last; |
131 /^y|^$/i and system "rndc reload"; |
131 } |
132 } else { |
132 |
133 print "** No PID of a running named found. Please reload manually.\n"; |
133 if ($pidfile) { |
134 } |
134 if ($opt_reload) { |
135 |
135 $_ = "y"; |
136 } |
136 print "** Nameserver will be reloaded\n"; |
|
137 } |
|
138 else { print "** Reload now? [Y/n]: "; $_ = <STDIN>; } |
|
139 /^y|^$/i and system "rndc reload"; |
|
140 } |
|
141 else { |
|
142 print |
|
143 "** No PID of a running named found. Please reload manually.\n"; |
|
144 } |
|
145 |
|
146 } |
137 } |
147 } |
138 |
148 |
139 { |
149 { |
140 my $date; |
150 my $date; |
141 sub next_serial($) { |
151 |
142 if (not defined $date) { |
152 sub next_serial($) { |
143 my ($dd, $mm, $yy) = (localtime)[3..5]; |
153 if (not defined $date) { |
144 $date = sprintf "%04d%02d%02d" => $yy < 1900 ? $yy + 1900 : $yy, $mm + 1, $dd; |
154 my ($dd, $mm, $yy) = (localtime)[3 .. 5]; |
145 } |
155 $date = sprintf "%04d%02d%02d" => $yy < 1900 ? $yy + 1900 : $yy, |
146 |
156 $mm + 1, $dd; |
147 $_[0] =~ /(?<date>\d{8})(?<cnt>\d\d)/; |
157 } |
148 return $date . sprintf("%02d", $+{cnt}+1) if $date eq $+{date}; |
158 |
149 return "${date}00"; |
159 $_[0] =~ /(?<date>\d{8})(?<cnt>\d\d)/; |
150 } |
160 return $date . sprintf("%02d", $+{cnt} + 1) if $date eq $+{date}; |
|
161 return "${date}00"; |
|
162 } |
151 } |
163 } |
152 |
164 |
153 __END__ |
165 __END__ |
154 |
166 |
155 =head1 NAME |
167 =head1 NAME |