#!/usr/bin/perl -w-
# --------------------------------------------------------------------- #
# reconf-exim4andbind - Modify named.conf.local and reconfig named #
# when necessary. This is intended to allow #
# lsa-svr to forward DNS request from outside #
# to students' VM, by monitoring update request #
# that will generate 'no authoritative' message #
# in daemon.log file. #
# - Plus add exim4 relay_domains config. #
# #
## version 0.1 - cj (2012-11-28) init. #
## version 0.2 - cj (2012-12-02) modified form gen-lsa-namedconflocal #
## version 0.2a - cj (2012-12-11) some cosmetic added. #
# version 0.3 - cj (2012-12-13) change to reconf-exim4andbind #
# --------------------------------------------------------------------- #
use strict;
my $exim4updateconf = "/var/cache/exim4/update-exim4.conf.conf";
my $namedconflocal = "/var/cache/bind/conf/named.conf.local";
my $daemonlogfile = "/var/log/daemon.log";
my $namedconfnewfile = "${namedconflocal}.new";
my $exim4confnewfile = "${exim4updateconf}.new";
my $logfile = "/var/log/named/reconf-dnsandmail.log";
my %zonelist = ();
my %tm = (); # last seen for that specific zone
my $TOO_OLD = 600; # Number of seconds since last seen
# Append log message to log file
sub logappend() {
my ($mesg) = @_;
my $DATE = `date "+%Y%m%d %H:%M:%S"`; chomp($DATE);
open LOG, ">> $logfile" or die "Can't append to log file : $!\n";
print LOG "$DATE\t$mesg\n";
close LOG;
}
# Check whether that provide IP is alive by using icmp 'ping' check
sub isAlive() {
my ($ip) = @_;
# Make sure that the IP is defined and actual ip number
# before send it to fping command.
return 0 unless defined($ip);
return 0 unless $ip =~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/;
my $cmd = "fping -a $ip 2> /dev/null";
# then do it.
open CMD, "$cmd |"
or die "Can't execute command '$cmd'\n";
my $ret = <CMD>;
close CMD;
# check the result.
return 0 unless defined($ret);
chomp($ret);
return "$ip" eq "$ret";
}
# Generate new named.conf.local file, and run "rndc reconfig"
sub newBindConf() {
my $date = `date "+%Y-%m-%d %H:%M"`;
chomp($date);
if (-f $namedconfnewfile ) { unlink $namedconfnewfile; }
die "Can't remove old '$namedconfnewfile', bail out!\n" if (-f $namedconfnewfile );
my $zonecount = 0;
open OUTPUT, "> $namedconfnewfile" or die "Can't create '$namedconfnewfile' : $!\n";
for my $zone (keys %zonelist) {
my $ip = $zonelist{$zone}->{'ip'};
$zonecount ++;
print OUTPUT << "EOT";
zone "$zone" { type forward; forward only; forwarders { $ip; }; };
EOT
}
close OUTPUT;
$date =~ s/[-: ]//g;
# New named.conf.local file
rename $namedconflocal, "${namedconflocal}-${date}"
or die "Can't backup original $namedconflocal file\n";
rename $namedconfnewfile, $namedconflocal
or die "Can't rename new $namedconflocal file\n";
system("rndc reconfig");
&logappend("Generate new Bind config with $zonecount zones ...");
}
# Generate new update-exim4.conf.conf file, and run "service exim4 reload"
sub newExim4Conf() {
my $date = `date "+%Y-%m-%d %H:%M"`;
chomp($date);
if (-f $exim4confnewfile ) { unlink $exim4confnewfile; }
die "Can't remove old '$exim4confnewfile', bail out!\n" if (-f $exim4confnewfile );
my $newrelaydomains = "";
my $zonecount = 0;
for my $zone (keys %zonelist) {
$newrelaydomains .= "$zone;";
$zonecount ++;
}
chop($newrelaydomains); # get rid of the last ';'
$date =~ s/[-: ]//g;
# New update-exim4.conf.conf file
open CONF, "$exim4updateconf" or die "Can't open $exim4updateconf file : $!\n";
open OUTPUT, "> $exim4confnewfile" or die "Can't create $exim4confnewfile : $!\n";
while (<CONF>) {
if (!/^dc_relay_domains/) {
print OUTPUT $_;
next;
}
print OUTPUT "dc_relay_domains='$newrelaydomains'\n";
}
close OUTPUT;
close CONF;
rename $exim4updateconf, "${exim4updateconf}-${date}"
or die "Can't backup original $exim4updateconf file\n";
rename $exim4confnewfile, $exim4updateconf
or die "Can't rename new $exim4updateconf file\n";
system("sudo service exim4 reload");
&logappend("Generate new Exim4 config with $zonecount zones ...");
}
# Timeout Check for zonelist
sub timeoutCheck() {
my $now = time();
my %newzonelist = ();
my $ret = 0;
for my $zone (keys %zonelist) {
my $ip = $zonelist{$zone}->{'ip'};
my $t = $zonelist{$zone}->{'time'};
if (&isAlive($ip)) {
$newzonelist{$zone}->{'ip'} = $ip;
$newzonelist{$zone}->{'time'} = $now;
} elsif (($now - $t) < $TOO_OLD) {
$newzonelist{$zone}->{'ip'} = $ip;
$newzonelist{$zone}->{'time'} = $t;
} else {
$ret = 1;
}
}
%zonelist = %newzonelist;
return $ret;
}
# Regular expression for match 'not authoriative' line.
my $MSTR = "(.{15}) lsa-svr named\[[0-9]+\]: " .
"client ([\.0-9]+)#[0-9]+: received notify for zone " .
"'([^']+)': not authoritative\$";
my ($lastsize, $thissize);
open INPUT, "$daemonlogfile" or die "Can't open '$daemonlogfile' : $!\n";
while (1) {
my $newzone = 0;
my $zonetimeout = 0;
my %tmpzonelist = ();
while (my $line = <INPUT>) {
chomp($line);
next unless $line =~ /received notify for zone/ ;
my ($tstamp,$ip,$zone) = ($line =~ /$MSTR/);
if (!defined($tstamp) or !defined($ip) or !defined($zone)) {
&logappend("ERROR:[$line]");
next;
}
$tmpzonelist{$zone}->{'ip'} = $ip;
&logappend("$tstamp $ip $zone");
print "LOG: $tstamp $ip $zone\n";
}
for my $zone (keys %tmpzonelist) {
my $ip = $tmpzonelist{$zone}->{'ip'};
if (&isAlive($ip)) {
$newzone++;
my $t = time();
$zonelist{$zone}->{'ip'} = $ip;
$zonelist{$zone}->{'time'} = $t;
print "ZONE: $t $ip $zone\n";
}
}
$lastsize = (stat "$daemonlogfile")[7];
do {
# Generate new config file only when there are new zones
# or host for that zone is not active for sometimes.
if (&timeoutCheck() or $newzone) {
&newBindConf();
&newExim4Conf();
$newzone = 0;
}
sleep(10);
$thissize = (stat "$daemonlogfile")[7];
} while ($lastsize == $thissize);
if ($thissize < $lastsize) {
close INPUT;
open INPUT, "$daemonlogfile" or
die "Can't open '$daemonlogfile' : $!\n";
&logappend("[ --- Reopen new daemon.log file --- ]");
print "LOG: --- new daemon.log ---\n";
}
}
close INPUT;
# --------------------------------------------------------------------- #
# end of file. #
# --------------------------------------------------------------------- #
Comments