diff -r 098af5defd01 -r 6f5ef6fb479d master_watcher --- a/master_watcher Mon Jan 27 15:42:03 2003 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,349 +0,0 @@ -#! /usr/bin/perl -w -# $Id$ -my $USAGE = <<'#'; -Usage: $ME [options] - -l --logfile=s Name of the logfile we've to read [$opt_logfile] - -z --zonesdir=s Where the named.conf's are expected [$opt_zonesdir] - -u --[no]update Update the \"masters\"-entries [$opt_update] - -f --[no]follow Follow the end of the logfile [$opt_follow] - -d --[no]debug extra debug output [$opt_debug] - -h --help This text [$opt_help] - --daemon go into background [$opt_daemon] - -p --pidfile=s file to store the pid [$opt_pidfile] - -v --version print version [$opt_version] -# -# Es wird ein Verzeichnis geben, in diesem Verzeichnis liegt für -# *jede* Zone eine eigene Konfigurations-Datei. -# Diese ganzen Konfigurationsdateien werden dann zusammengefaßt -# und diese zusammengefaßte wird dem bind per "include" mitgeteilt. -# -# Wir durchsuchen den Syslog nach -# NOTIFY von Master-Servern. -# o Wenn der Master nicht authorisiert ist -> ENDE GELÄNDE -# o Andernfalls merken wir uns diesen Master für diese Domain -# Wenn dann mal zu lesen ist "not of our zones" und wir uns diesen -# als einen unserer Master gemerkt haben, dann vermerken wir uns, daß -# für diese Zone das Konfig-File fehlt. -# -# Sollte irgendwann mal ein "slave zone...loaded" auftauchen, ist das Konfig-File -# inzwischen vorhanden und kein Grund zur Panik. Wir entfernen es aus der Liste -# der fehlenden Files. -# -# Sollte dieser Text ausbleiben, müssen wir ein File anlegen (wahrscheinlich). -# -> Sollte trotzdem schon eins da sein, dann konnten wir aus irgendwelchen -# Gründen nix laden (deshalb fehlt ja der "...loaded"-Text). Das kann z.B. sein, weil ein -# Master mal keinen Bock hat oder nicht authoritativ ist. -# -# Etwas anders sieht's im update-Modus aus. Hier wird *jeder* Master für jede Domain gemerkt und -# geprüft, ob der in dem entsprechenden Konfig-File enthalten ist. -# -# Im täglichen Einsatz sollte es ohne Update-Modus ganz gut funktionieren. - -use strict; -use File::Basename; -use IO::Handle; -use Fcntl qw/:flock/; -use File::Path; -use Getopt::Long; -use Unix::Syslog qw/:macros :subs/; - -my $ME = basename $0; - - -my %auth = ( - "212.80.235.130" => "pu.schlittermann.de", - "212.80.235.132" => "mango.compot.com", - "145.253.160.50" => "bastion.actech.de", - "62.144.175.34" => "ns.add-on.de", - "195.145.19.34" => "ns.datom.de", - "62.157.194.1" => "ns.mueritzcomp.de", - "212.80.235.137" => "ns.flaemingnet.de omni.flaemingnet.de", - "212.80.235.152" => "www.proton24.de", - # "194.162.141.17" => "dalx1.nacamar.de", -); - -$SIG{__DIE__} = sub { syslog(LOG_ERR, $_[0]); exit -1; }; -$SIG{__WARN__} = sub { syslog(LOG_WARNING, $_[0]); }; -$SIG{TERM} = $SIG{INT} = sub { exit 0; }; - -my %seen; - -my $opt_help = 0; -my $opt_pidfile = "/var/run/$ME.pid"; -my $opt_logfile = "/var/log/syslog"; -my $opt_zonesdir = "/etc/bind/zones.d"; -my $opt_follow = 0; -my $opt_update = 0; -my $opt_debug = 0; -my $opt_daemon = 1; -my $opt_version = 0; - -my $naptime = 60; - - -sub updateFile($$$); -sub debug($;@) { syslog(LOG_DEBUG, "DEBUG " . shift @_, @_) if $opt_debug; } - -END { - open(PID, $opt_pidfile); - my $pid = ; - close(PID); - unlink $opt_pidfile if $$ == $pid; -} - -MAIN: { - - - openlog($ME, LOG_PID | LOG_PERROR, LOG_DAEMON); - syslog(LOG_NOTICE, "starting"); - - GetOptions( - "help" => \$opt_help, - "logfile=s" => \$opt_logfile, - "follow!" => \$opt_follow, - "update!" => \$opt_update, - "debug!" => \$opt_debug, - "daemon!" => \$opt_daemon, - "pidfile=s" => \$opt_pidfile, - "version!" => \$opt_version, - "zonesdir=s" => \$opt_zonesdir) - or die "$ME: Bad Usage\n"; - - if ($opt_help) { - print eval "\"$USAGE\""; - exit 0; - } - - if ($opt_version) { - print "$ME Version: ", '$Id$', "\n"; - exit 0; - } - - - # Create the PID-File - { - open(PID, $_ = ">$opt_pidfile.$$") or die "Can't open $opt_pidfile: $!\n"; - print PID "$$\n"; - close(PID); - - if (!link($_ = "$opt_pidfile.$$", $opt_pidfile)) { - unlink "$opt_pidfile.$$"; - die "There's another $ME running. Bad. Stop."; - } - unlink "$opt_pidfile.$$"; - } - - if ($opt_daemon) { - my $pid = fork(); - - if ($pid < 0) { - die "Can't fork: $!\n"; - } - - if ($pid) { - open(PID, $_ = ">$opt_pidfile") or die "Can't open $_: $!\n"; - print PID "$pid\n"; - close(PID); - exit 0; - } - - close(STDIN); close(STDOUT); close(STDERR); - } - - - open (LOGFILE, $_ = "<$opt_logfile") or die "Can't open $_: $!\n"; - - for (;;) { - my (%masters, %missing, %nomasters); - while () { - - my ($domain, $ip); - - # NOTIFY-Zeilen - ($domain, $ip) = /NOTIFY.*?\((\S+?),.*?\[([\d.]+)\]/ and do { - if (not exists $auth{$ip}) { - warn "notify for $domain from unauthorized ip $ip\n"; - next; - }; - # also in die Liste (hier als Key eines Hashes wegen der möglichen - # Dopplungen) der Master für diese Domain aufnehmen. - debug("Master für $domain: $ip\n"); - $masters{$domain}->{$ip} = 1; - next; - }; - - # Das müssen wir doch garnicht machen, da wir ja sowieso nach - # dem Master-Files gucken... - # NOTIFY for vergessene - /NOTIFY for "(\S+?)".*not one of our zones/ and do { - my $domain = $1; - if (not exists $masters{$domain}) { - debug "skipping $domain (not authorized)\n"; - next; - } - # Also in die Liste der vergessenen Domains (für die wir garkeine - # Konfigurations-Datei haben) - next if exists $missing{$domain}; # schon erledigt - - debug("Missing file for $domain\n"); - $missing{$domain} = 1; - next; - }; - - # Wenn wir ein "... loaded" finden, dann fehlt das File nicht gänzlich! - /slave zone "(\S+?)" .*loaded/ and do { - my $domain = $1; - next if not exists $missing{$domain}; # ist noch nicht vermißt worden - - debug("Missing file for $domain is not longer missing\n"); - delete $missing{$domain}; - next; - }; - - /\[([\d.]+)\] not authoritative for (\S+), SOA/ and do { - my ($master, $domain) = ($1, $2); - next if exists $nomasters{$domain}->{$master}; - - debug "$master isn't a auth. master for $domain\n"; - $masters{$domain}->{$master} = 1; # sieht blöd aus, wird aber gebraucht, - # weil wir nur die bearbeiten, die einen - # master haben - $nomasters{$domain}->{$master} = 1; - next; - }; - } - - # Jetzt sind wir erstmal durch und verarbeiten alles - my $changed = 0; - foreach my $domain (sort ($opt_update ? keys %masters : keys %missing)) { - $changed += updateFile($domain, [keys %{$masters{$domain}}], [keys %{$nomasters{$domain}}]); - delete $masters{$domain}; - delete $missing{$domain} if exists $missing{$domain}; - delete $nomasters{$domain} if exists $nomasters{$domain}; - } - - debug "$changed changes."; - if ($changed) { - debug("bind reload required\n"); - open(ALL, $_ = ">/etc/bind/zones.all") or die "Can't open $_: $!\n"; - foreach () { - open(IN, $_) or die "Can't open $_: $!\n"; - print ALL ; - close(IN); - } - system qw(ndc reload); - } - - last if !$opt_follow; - syslog LOG_INFO, "Sleeping for $naptime seconds\n"; - sleep $naptime; - if ((LOGFILE->stat())[1] != (stat($opt_logfile))[1]) { - # new file to follow - syslog(LOG_NOTICE, "Logfile changed, re-open it.\n"); - open(LOGFILE, $_ = "<$opt_logfile") - or die "Can't open $_: $!\n"; - } else { - LOGFILE->clearerr(); - } - } -} - -sub updateFile($$$) -{ - local $_; - my $domain = $_[0]; - my @new_masters = @{$_[1]}; - my @no_masters = @{$_[2]}; - - my %masters = (); - my $masters; - - my $file = "$opt_zonesdir/$domain"; - - debug "updateFile: $domain, @new_masters, @no_masters\n"; - - if (-f $file) { - # Das File ist also schon da, wir müssen nur mal gucken, ob die Master, - # von denen wir ein NOTIFY erhalten haben, auch in unserer Datei stehen. - # - open (F, $_ = "+<$file") or die "Can't open $_: $!\n"; - flock(F, LOCK_EX); seek(F, 0, 0); - - $_ = join "", ; - - # Liste der Master raussuchen, darus noch die löschen, die uns - # die Mitarbeit verweigert haben.. - /^(\s*masters\s*{\s*)(.*?);(\s*}\s*;)(\s*\/\/.*?\n)/ims; - %masters = map { $_, 1 } split/\s*;\s*/, $2; - $masters = %masters; # für den späteren Vergleich - - # noch unsere neuen hinzufügen... - @masters{@new_masters} = map { 1 } @new_masters; - - # nun die weg, die sich nicht zuständig fühlen - delete @masters{@no_masters}; - - # Wenn sich nach alldem nichts verändert hat, haben wir fertig. - if ($masters eq %masters) { - debug("File is up-to-date for $domain\n"); - syslog(LOG_NOTICE, "No changes made for $domain (no \"loaded\" seen, defective master?)\n") - unless $opt_update; - close(F); - return 0; - } - - if (not %masters) { - syslog LOG_NOTICE, "REMOVING $file (empty masters list)\n"; - close F; - unlink $file; - return 1; - } - - $masters = join ";", keys %masters; - syslog(LOG_NOTICE, "Updated masters ($masters) list for $domain\n"); - s/^(\s*masters\s*{\s*)(.*?);(\s*}\s*;)/$1$masters;$3/ims; - - truncate(F, 0); - seek(F, 0, 0); - print F; - close F; - - return 1; - } - - - my $date = localtime(); - my %new_masters = map { $_, 1 } @new_masters; - delete @new_masters{@no_masters}; - - if (not %new_masters) { - syslog LOG_INFO, "not creating $file (empty masters list)\n"; - return 0; - } - - $masters = join "; ", @new_masters; - - -d $opt_zonesdir or mkpath($opt_zonesdir, 0, 0755); - - syslog(LOG_NOTICE, "Creating $file for $domain"); - open(OUT, $_ = ">$file") or die "Can't open $_: $!\n"; - - print OUT <<_EOF_; -// Autogenerated by $ME: $date -zone "$domain" { - type slave; - masters { $masters; }; // $date - file "/etc/bind/slave/$domain"; - allow-query { any; }; - allow-transfer { none; }; - allow-update { none; }; -}; - -_EOF_ - close OUT; - - return 1; -} - - -# vim:sts=4 sw=4 aw ai sm: