master_watcher
changeset 4 9665d5582b40
parent 3 2699119ec0ea
child 5 96a0d63303c6
equal deleted inserted replaced
3:2699119ec0ea 4:9665d5582b40
     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);
    60     GetOptions(
    83     GetOptions(
    61 	"help" => \$opt_help,
    84 	"help" => \$opt_help,
    62 	"logfile=s" => \$opt_logfile,
    85 	"logfile=s" => \$opt_logfile,
    63 	"follow!" => \$opt_follow,
    86 	"follow!" => \$opt_follow,
    64 	"update!" => \$opt_update,
    87 	"update!" => \$opt_update,
    65 	"create!" => \$opt_create,
    88 	"debug!" => \$opt_debug,
    66 	"dir=s" => \$opt_zonesdir)
    89 	"zonesdir=s" => \$opt_zonesdir)
    67     or die "$ME: Bad Usage\n";
    90     or die "$ME: Bad Usage\n";
    68 
    91 
    69     if ($opt_help) {
    92     if ($opt_help) {
    70 	print eval "\"$USAGE\"";
    93 	print eval "\"$USAGE\"";
    71 	exit 0;
    94 	exit 0;
    72     }
    95     }
    73 
    96 
    74     open (LOGFILE, $_ = "<$opt_logfile") or die "Can't open $_: $!\n";
    97     open (LOGFILE, $_ = "<$opt_logfile") or die "Can't open $_: $!\n";
       
    98     $inode = (LOGFILE->stat)[1];
    75     for (;;) {
    99     for (;;) {
    76 	my (%masters, my %missing);
   100 	my (%masters, my %missing);
    77 	while (<LOGFILE>) {
   101 	while (<LOGFILE>) {
    78 	    
   102 	    
    79 	    my ($domain, $ip);
   103 	    my ($domain, $ip);
    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";
   172 };
   247 };
   173 
   248 
   174 _EOF_
   249 _EOF_
   175     close OUT;
   250     close OUT;
   176 
   251 
   177     return;
   252     return 1;
   178 }
   253 }
   179 	
   254 	
   180 
   255 
   181 # vim:sts=4 sw=4 aw ai sm:
   256 # vim:sts=4 sw=4 aw ai sm: