1 #! /usr/bin/perl -w |
1 #! /usr/bin/perl -w |
2 my $USAGE = <<'#'; |
2 my $USAGE = <<'#'; |
3 Usage: $ME [options] |
3 Usage: $ME [options] |
4 -l --logfile=s Name of the logfile we've to read [$opt_logfile] |
4 -l --logfile=s Name of the logfile we've to read [$opt_logfile] |
5 -d --dir=s Where the named.conf's are expected [$opt_zonesdir] |
5 -z --zonesdir=s Where the named.conf's are expected [$opt_zonesdir] |
6 -u --[no]update Update the "masters"-entries [$opt_update] |
6 -u --[no]update Update the "masters"-entries [$opt_update] |
7 -c --[no]create Add newly appeared domains [$opt_create] |
|
8 -f --[no]follow Follow the end of the logfile [$opt_follow] |
7 -f --[no]follow Follow the end of the logfile [$opt_follow] |
|
8 -d --[no]debug extra debug output [$opt_debug] |
9 -h --help This text [$opt_help] |
9 -h --help This text [$opt_help] |
10 # |
10 # |
11 # Es wird ein Verzeichnis geben, in diesem Verzeichnis liegt für |
11 # Es wird ein Verzeichnis geben, in diesem Verzeichnis liegt für |
12 # *jede* Zone eine eigene Konfigurations-Datei. |
12 # *jede* Zone eine eigene Konfigurations-Datei. |
13 # Diese ganzen Konfigurationsdateien werden dann zusammengefaßt |
13 # Diese ganzen Konfigurationsdateien werden dann zusammengefaßt |
14 # und diese zusammengefaßte wird dem bind per "include" mitgeteilt. |
14 # und diese zusammengefaßte wird dem bind per "include" mitgeteilt. |
15 |
15 # |
16 #use strict; |
16 # Wir durchsuchen den Syslog nach |
|
17 # NOTIFY von Master-Servern. |
|
18 # o Wenn der Master nicht authorisiert ist -> ENDE GELÄNDE |
|
19 # o Andernfalls merken wir uns diesen Master für diese Domain |
|
20 # Wenn dann mal zu lesen ist "not of our zones" und wir uns diesen |
|
21 # als einen unserer Master gemerkt haben, dann vermerken wir uns, daß |
|
22 # für diese Zone das Konfig-File fehlt. |
|
23 # |
|
24 # Sollte irgendwann mal ein "slave zone...loaded" auftauchen, ist das Konfig-File |
|
25 # inzwischen vorhanden und kein Grund zur Panik. Wir entfernen es aus der Liste |
|
26 # der fehlenden Files. |
|
27 # |
|
28 # Sollte dieser Text ausbleiben, müssen wir ein File anlegen (wahrscheinlich). |
|
29 # -> Sollte trotzdem schon eins da sein, dann konnten wir aus irgendwelchen |
|
30 # Gründen nix laden (deshalb fehlt ja der "...loaded"-Text). Das kann z.B. sein, weil ein |
|
31 # Master mal keinen Bock hat oder nicht authoritativ ist. |
|
32 # |
|
33 # Etwas anders sieht's im update-Modus aus. Hier wird *jeder* Master für jede Domain gemerkt und |
|
34 # geprüft, ob der in dem entsprechenden Konfig-File enthalten ist. |
|
35 # |
|
36 # Im täglichen Einsatz sollte es ohne Update-Modus ganz gut funktionieren. |
|
37 |
|
38 use strict; |
17 use File::Basename; |
39 use File::Basename; |
18 use IO::Handle; |
40 use IO::Handle; |
|
41 use Fcntl qw/:flock/; |
19 use File::Path; |
42 use File::Path; |
20 use Getopt::Long; |
43 use Getopt::Long; |
21 use Unix::Syslog qw/:macros :subs/; |
44 use Unix::Syslog qw/:macros :subs/; |
22 |
45 |
23 my $ME = basename $0; |
46 my $ME = basename $0; |
24 |
47 |
25 |
48 |
26 my %auth = ( |
49 my %auth = ( |
27 "212.172.233.34" => "pu.schlittermann.de", |
50 "212.80.235.130" => "pu.schlittermann.de", |
28 "212.80.235.130" => "pu2.schlittermann.de", |
51 "212.80.235.132" => "mango.compot.com", |
29 "145.253.160.50" => "bastion.actech.de", |
52 "145.253.160.50" => "bastion.actech.de", |
30 "212.172.233.146" => "ns.flaemingnet.de", |
|
31 "212.172.127.34" => "mango.compot.com", |
|
32 "62.144.175.34" => "ns.add-on.de", |
53 "62.144.175.34" => "ns.add-on.de", |
33 "195.145.19.34" => "ns.datom.de", |
54 "195.145.19.34" => "ns.datom.de", |
|
55 # "194.162.141.17" => "dalx1.nacamar.de", |
34 ); |
56 ); |
35 |
57 |
36 $SIG{__DIE__} = sub { syslog(LOG_ERR, $_[0]); exit -1; }; |
58 $SIG{__DIE__} = sub { syslog(LOG_ERR, $_[0]); exit -1; }; |
37 $SIG{__WARN__} = sub { syslog(LOG_WARNING, $_[0]); }; |
59 $SIG{__WARN__} = sub { syslog(LOG_WARNING, $_[0]); }; |
38 |
60 |
39 my %seen; |
61 my %seen; |
40 |
62 |
41 my $opt_help = 0; |
63 my $opt_help = 0; |
42 my $opt_logfile = "./syslog"; |
64 my $opt_logfile = "/var/log/syslog"; |
43 my $opt_zonesdir = "./zones"; |
65 my $opt_zonesdir = "/etc/bind/zones.d"; |
44 my $opt_follow = 0; |
66 my $opt_follow = 0; |
45 my $opt_create = 1; |
67 my $opt_update = 0; |
46 my $opt_update = 1; |
68 my $opt_debug = 0; |
47 |
69 |
48 my $naptime = 1; |
70 my $naptime = 60; |
|
71 my $inode = 0; |
49 |
72 |
50 |
73 |
51 sub updateFile($@); |
74 sub updateFile($@); |
52 |
75 sub debug($;@) { syslog(LOG_DEBUG, "DEBUG " . shift @_, @_) if $opt_debug; } |
53 |
76 |
54 MAIN: { |
77 MAIN: { |
55 |
78 |
56 |
79 |
57 openlog($ME, LOG_PID | LOG_PERROR, LOG_DAEMON); |
80 openlog($ME, LOG_PID | LOG_PERROR, LOG_DAEMON); |
82 ($domain, $ip) = /NOTIFY.*?\((\S+?),.*?\[([\d.]+)\]/ and do { |
106 ($domain, $ip) = /NOTIFY.*?\((\S+?),.*?\[([\d.]+)\]/ and do { |
83 if (not exists $auth{$ip}) { |
107 if (not exists $auth{$ip}) { |
84 warn "notify for $domain from unauthorized ip $ip\n"; |
108 warn "notify for $domain from unauthorized ip $ip\n"; |
85 next; |
109 next; |
86 }; |
110 }; |
|
111 # also in die Liste (hier als Key eines Hashes wegen der möglichen |
|
112 # Dopplungen) der Master für diese Domain aufnehmen. |
|
113 debug("Master für $domain: $ip\n"); |
87 $masters{$domain}->{$ip} = 1; |
114 $masters{$domain}->{$ip} = 1; |
88 next; |
115 next; |
89 }; |
116 }; |
90 |
117 |
|
118 # Das müssen wir doch garnicht machen, da wir ja sowieso nach |
|
119 # dem Master-Files gucken... |
91 # NOTIFY for vergessene |
120 # NOTIFY for vergessene |
92 /NOTIFY for "(\S+?)".*not one of our zones/ and do { |
121 /NOTIFY for "(\S+?)".*not one of our zones/ and do { |
93 if (not exists $masters{$1}) { |
122 my $domain = $1; |
94 warn "skipping $1 (not authorized)\n"; |
123 if (not exists $masters{$domain}) { |
|
124 debug "skipping $domain (not authorized)\n"; |
95 next; |
125 next; |
96 } |
126 } |
97 $missing{$1} = 1; |
127 # Also in die Liste der vergessenen Domains (für die wir garkeine |
|
128 # Konfigurations-Datei haben) |
|
129 next if exists $missing{$domain}; # schon erledigt |
|
130 |
|
131 debug("Missing file for $domain\n"); |
|
132 $missing{$domain} = 1; |
98 }; |
133 }; |
|
134 |
|
135 # Wenn wir ein "... loaded" finden, dann fehlt das File nicht gänzlich! |
|
136 /slave zone "(\S+?)" .*loaded/ and do { |
|
137 my $domain = $1; |
|
138 next if not exists $missing{$domain}; # ist noch nicht vermißt worden |
|
139 |
|
140 debug("Missing file for $domain is not longer missing\n"); |
|
141 delete $missing{$domain}; |
|
142 }; |
99 } |
143 } |
100 |
144 |
101 # Jetzt sind wir erstmal durch und verarbeiten alles |
145 # Jetzt sind wir erstmal durch und verarbeiten alles |
102 |
146 my $changed = 0; |
103 #foreach my $domain (sort keys %missing) { |
147 foreach my $domain (sort ($opt_update ? keys %masters : keys %missing)) { |
104 #updateFile($domain, keys %{$masters{$domain}}); |
148 $changed += updateFile($domain, keys %{$masters{$domain}}); |
105 #delete $masters{$domain}; |
|
106 #delete $missing{$domain}; |
|
107 #} |
|
108 |
|
109 foreach my $domain (sort keys %masters) { |
|
110 updateFile($domain, keys %{$masters{$domain}}); |
|
111 delete $masters{$domain}; |
149 delete $masters{$domain}; |
112 delete $missing{$domain} if exists $missing{$domain}; |
150 delete $missing{$domain} if exists $missing{$domain}; |
113 } |
151 } |
114 |
152 |
|
153 debug "$changed changes."; |
|
154 if ($changed) { |
|
155 debug("bind reload required\n"); |
|
156 open(ALL, $_ = ">/etc/bind/zones.all") or die "Can't open $_: $!\n"; |
|
157 foreach (</etc/bind/zones.d/*>) { |
|
158 open(IN, $_) or die "Can't open $_: $!\n"; |
|
159 print ALL <IN>; |
|
160 close(IN); |
|
161 } |
|
162 system qw(ndc reload); |
|
163 } |
|
164 |
115 last if !$opt_follow; |
165 last if !$opt_follow; |
|
166 debug("Sleeping for $naptime seconds\n"); |
116 sleep $naptime; |
167 sleep $naptime; |
117 # seek(LOGFILE, 0, 1); |
168 if ((LOGFILE->stat())[1] != $inode) { |
118 LOGFILE->clearerr(); |
169 # new file to follow |
|
170 syslog(LOG_NOTICE, "Logfile changed, re-open it.\n"); |
|
171 open(LOGFILE, $_ = "<$opt_logfile") |
|
172 or die "Can't open $_: $!\n"; |
|
173 $inode = (LOGFILE->stat())[1]; |
|
174 } else { |
|
175 LOGFILE->clearerr(); |
|
176 } |
119 } |
177 } |
120 } |
178 } |
121 |
179 |
122 sub updateFile($@) |
180 sub updateFile($@) |
123 { |
181 { |
124 local $_; |
182 local $_; |
125 my $domain = shift; |
183 my $domain = shift; |
126 my %masters = map { $_, 1 } @_; |
184 my %new_masters = map { $_, 1 } @_; |
|
185 my %old_masters = (); |
|
186 my $masters; |
127 my $file = "$opt_zonesdir/$domain"; |
187 my $file = "$opt_zonesdir/$domain"; |
128 |
188 |
129 if (-f $file) { |
189 if (-f $file) { |
130 return if not $opt_update; |
190 # Das File ist also schon da, wir müssen nur mal gucken, ob die Master, |
131 syslog(LOG_NOTICE, "Updating $file for $domain"); |
191 # von denen wir ein NOTIFY erhalten haben, auch in unserer Datei stehen. |
|
192 # Vorerst entfernen wir keinen Master, wir fügen lediglich welche hinzu, |
|
193 # wenn Sie noch nicht dabei sind. |
|
194 # |
132 open (F, $_ = "+<$file") or die "Can't open $_: $!\n"; |
195 open (F, $_ = "+<$file") or die "Can't open $_: $!\n"; |
133 # ein etwas anderer Versuch - noch nicht fertig |
196 flock(F, LOCK_EX); seek(F, 0, 0); |
|
197 |
134 $_ = join "", <F>; |
198 $_ = join "", <F>; |
135 |
199 |
136 # Liste der Master raussuchen |
200 # Liste der Master raussuchen |
137 /^(\s*masters\s*{\s*)(.*?);(\s*}\s*;)/ims; |
201 /^(\s*masters\s*{\s*)(.*?);(\s*}\s*;)(\s*\/\/.*?\n)/ims; |
138 |
202 %old_masters = map { $_, 1 } split/\s*;\s*/, $2; |
139 foreach (split /\s*;\s*/, $2) { |
203 |
140 $masters{$_} = 1; |
204 # Aus den neuen löschen wir die, die bereits bekannt sind |
141 } |
205 delete @new_masters{keys %old_masters}; |
142 $masters = join "; ", keys %masters; |
206 |
|
207 # Wenn nun noch welche übring sind, dann müssen wir diese |
|
208 # mit eintragen. Ansonsten haben wir fertig. |
|
209 if (not %new_masters) { |
|
210 debug("Uptodate file for $domain\n"); |
|
211 syslog(LOG_NOTICE, "No changes made for $domain (no "loaded" seen, defective master?)\n") |
|
212 unless $opt_update; |
|
213 close(F); |
|
214 return 0; |
|
215 } |
|
216 |
|
217 syslog(LOG_NOTICE, "Updated masters list for $domain\n"); |
|
218 $masters = join ";", keys %old_masters, keys %new_masters; |
143 s/^(\s*masters\s*{\s*)(.*?);(\s*}\s*;)/$1$masters;$3/ims; |
219 s/^(\s*masters\s*{\s*)(.*?);(\s*}\s*;)/$1$masters;$3/ims; |
144 |
220 |
145 truncate(F, 0); |
221 truncate(F, 0); |
146 seek(F, 0, 0); |
222 seek(F, 0, 0); |
147 print F; |
223 print F; |
148 close F; |
224 close F; |
149 |
225 |
150 return; |
226 return 1; |
151 } |
227 } |
152 |
228 |
153 return if not $opt_create; |
|
154 |
229 |
155 my $date = localtime(); |
230 my $date = localtime(); |
156 my $masters = join "; ", keys %masters; |
231 $masters = join "; ", keys %new_masters; |
157 |
232 |
158 -d $opt_zonesdir or mkpath($opt_zonesdir, 0, 0755); |
233 -d $opt_zonesdir or mkpath($opt_zonesdir, 0, 0755); |
159 |
234 |
160 syslog(LOG_NOTICE, "Creating $file for $domain"); |
235 syslog(LOG_NOTICE, "Creating $file for $domain"); |
161 open(OUT, $_ = ">$file") or die "Can't open $_: $!\n"; |
236 open(OUT, $_ = ">$file") or die "Can't open $_: $!\n"; |