#!/usr/bin/perl -w
+# This is chiark-named-conf, which is Copyright 2002 Ian Jackson.
+#
+# chiark-named-conf and its manpage are free software; you can
+# redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+#
+# chiark-named-conf and its manpage are distributed in the hope that
+# it will be useful, but WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
use strict;
use IO::File;
use POSIX;
use Fcntl qw(:DEFAULT :flock);
-use vars qw($quis
+# bastard Perl wants me to do this now !
+sub loarg();
+sub soarg();
+sub usageerr ($);
+sub cfg_fail ($);
+sub read_config ($);
+sub qualify ($);
+sub bad_modifiers ($);
+sub zone_conf ($$$$$@);
+sub set_output($);
+sub progress ($$);
+sub verbose ($);
+sub process_zones (@);
+sub zone_warning ($$);
+sub zone_warnmore ($);
+sub zone_check_full ();
+sub zone_reset();
+sub zone_investigate();
+sub zone_check_nsrrset ($$$$);
+sub zone_ns_name ($$);
+sub zone_server_queue ($$$$$);
+sub zone_server_addr ($$$$$);
+sub zone_check_soa ($$$$);
+sub zone_consistency();
+sub zone_servers_ok ();
+sub zone_consistency_set ($%);
+sub zone_check_local ();
+sub zone_servers_simplefind ();
+sub zone_server_simple ($$$);
+sub zone_style ($$);
+sub mail_zone_before ();
+sub mail_zone_after ();
+sub pmail ($);
+sub ptime ($);
+sub mail_zone_mail ();
+sub zone_output ();
+sub output_files ();
+sub debug_dump ($);
+sub debug_trace ($);
+sub has_suffix_of ($$);
+sub lookup ($$$$);
+sub dig (&$$$);
+sub domain_canon ($$);
+
+use vars qw($quis $stdout_fh $stderr_fh
$mode $doall $domail
$etcfile $where
$debug $needglue $localonly $repeat $verbosity
$progress_fh $warn_fh $modifiers
%group2modcmd %group2used);
+$|=1;
$quis= $0; $quis =~ s,.*/,,;
$mode= '';
$admin=''; $mail_state_dir=''; $mail_max_warnfreq= 50;
$repeat= 0;
$domail= '';
-$progress_fh= 'STDOUT';
-$warn_fh= 'STDERR';
$modifiers= '';
+$group2modcmd{'foreign'}= '$!*@?';
+$group2used{'foreign'}= 1;
+($progress_fh= $stdout_fh= new_from_fd IO::Handle(1,'w') and
+ $warn_fh= $stderr_fh = new_from_fd IO::Handle(2,'w'))
+ or die "$quis: setup standard filehandles: $!\n";
+
use vars qw($dig_owner $dig_type $dig_rdata);
while (@ARGV && $ARGV[0] =~ m/^\-/) {
last if m/^$/;
if (m/^(yes|no|force)$/) { m/^./; $mode= $&; $domail=''; }
elsif (m/^nothing$/) { $mode= 'x'; $domail=''; }
- elsif (m/^mail\-(first|middle|final|final-test)/) {
+ elsif (m/^mail\-(first|middle|final|final\-test)$/) {
$mode='n';
$domail=$1;
}
usageerr("-v may be specified at most once") if $verbosity>3;
usageerr("-D may be specified at most twice") if $debug>2;
usageerr("must specify either -f|-y|-n or zones (and not both)")
- if !!$mode == !!@ARGV;
+ if !!$mode == !!@ARGV && !$domail;
sub usageerr ($) {
die <<END;
$slave_prefix= '';
$slave_suffix= '';
-use vars qw(@self_ns @self_soa @self_addr @forbid_addr @conv_glueless);
-@self_ns= @self_soa= @self_addr= @forbid_addr= ();
+use vars qw(@self_ns @self_soa @self_addr @forbid_addr @forbid_slave
+ @conv_glueless @indirect_glue);
+@self_ns= @self_soa= @self_addr= @forbid_addr= @forbid_slave= @indirect_glue= ();
@conv_glueless= qw(in-addr.arpa ip6.arpa ip6.int);
use vars qw(%zone_cfg @zone_cfg_list);
read_config($etcfile);
debug_dump('@zone_cfg_list %zone_cfg');
-process_zones($mode ? @zone_cfg_list : @ARGV);
+process_zones(!@ARGV ? @zone_cfg_list : @ARGV);
debug_dump('%output_contents');
output_files() if $install;
+$stdout_fh->close or die "$quis: write messages to stdout: $!\n";
+$stderr_fh->close or die "$quis: write messages to stderr: $!\n";
exit 0;
#-------------------- configuration reading
$mod,$dir,$prefix,$suffix,$subfile,$lprefix,$lsuffix,$zf);
local ($_);
- $fh= new IO::File $if,'r' or cfg_fail("open $if:\n $!");
+ $fh= new IO::File $if,'r';
+ unless ($fh) {
+ return if $! == &ENOENT;
+ cfg_fail("open $if:\n $!");
+ }
$before= '';
for (;;) {
if (!defined($_= <$fh>)) {
@self_soa= @self if $1 ne '-ns';
} elsif (m/^serverless\-glueless\s+(\S.*\S)/) {
@conv_glueless= split /\s+/, $1;
+ } elsif (m/^allow\-indirect\-glue\s+(\S.*\S)/) {
+ @indirect_glue= split /\s+/, $1;
} elsif (m/^self\-addr\s+([0-9. \t]+)/) {
@self_addr= split /\s+/, $1;
} elsif (m/^forbid\-addr(?:\s+([0-9. \t]+))?/) {
@forbid_addr= defined $1 ? split /\s+/, $1 : ();
+ } elsif (m/^forbid\-slave(?:\s+([0-9. \t]+))?/) {
+ @forbid_slave= defined $1 ? split /\s+/, $1 : ();
} elsif (m,^
primary\-dir (\W*)
\s+ (\S+)/([^/ \t]*)
local ($_) = @_;
if (!eval {
die "bad modifier $&" if m/[^!*\$\@~?]/;
- die "repeated modifier $1" if m/(.).*$1/;
+ die "repeated modifier $1" if m/(.).*\1/;
1;
}) {
$@ =~ s/\n//;
return 0;
}
+sub zone_conf_settings ($$) {
+ my ($cfg,$zone) = @_;
+ my ($sfx,$aref);
+ foreach $sfx (qw(self_soa self_ns self_addr forbid_addr forbid_slave
+ conv_glueless indirect_glue)) {
+ { no strict 'refs'; $aref= [ @$sfx ]; }
+ @$aref or cfg_fail("failed to specify $sfx before zone")
+ if $sfx =~ m/^self/;
+ $cfg->{$sfx}= $aref;
+ }
+ foreach $sfx (qw(self_soa self_ns)) {
+ map { s/\*$/$zone/ } @{ $zone_cfg{$zone}{$sfx} };
+ }
+}
+
sub zone_conf ($$$$$@) {
my ($zone,$style,$sabbr,$mod,$file,@servers) = @_;
- my ($sfx,$aref);
$file= qualify("$slave_dir/$slave_prefix".$zone.$slave_suffix)
unless length $file;
if (!length $output) {
$zone_cfg{$zone}{'maildir'}= qualify($mail_state_dir);
$zone_cfg{$zone}{'mailmwarn'}= $mail_max_warnfreq;
}
- foreach $sfx (qw(self_soa self_ns self_addr forbid_addr conv_glueless)) {
- { no strict 'refs'; $aref= [ @$sfx ]; }
- @$aref or cfg_fail("failed to specify $sfx before zone")
- if $sfx =~ m/^self/;
- $zone_cfg{$zone}{$sfx}= $aref;
- }
- foreach $sfx (qw(self_soa self_ns)) {
- $zone_cfg{$zone}{$sfx} =~ s/\*$/$zone/;
- }
+ zone_conf_settings($zone_cfg{$zone}, $zone);
$zone_cfg{$zone}{'output'}= $output;
push @zone_cfg_list, $zone;
}
foreach $zone (@zones) {
$cfg= $zone_cfg{$zone} || {
'style_p' => 'foreign',
- 's' => 'f',
+ 's' => "f $group2modcmd{'foreign'}",
'servers' => [ ],
};
+ zone_conf_settings($cfg, $zone);
- mail_zone_before() if $domail;
+ mail_zone_before() or next
+ if $domail;
zone_reset();
progress(1, sprintf "%-20s %s", $zone, $$cfg{'style_p'});
if ($check && ($doall || !zone_style('?',0))) {
use vars qw(%delgs); # $delgs{$nameserver_list} = [ $whosaidandwhy ]
use vars qw(%auths); # $auths{$nameserver_list} = [ $whosaidandwhy ]
use vars qw(%glue); # $glue{$name}{$addr_list} = [ $whosaidandwhy ]
-use vars qw(%soas); # $soa{"$serial $origin"} = [ $whosaidandwhy ]
+use vars qw(%soas); # $soa{"$serial $mname"} = [ $whosaidandwhy ]
use vars qw(%addr_is_ok %warned);
use vars qw($delg_to_us);
use vars qw(@to_check); # ($addr,$whyask,$name_if_auth,$glueless_ok, ...)
sub zone_check_nsrrset ($$$$) {
my ($uaddr,$wa, $name_if_auth, $glueless_ok) = @_;
- my (@s, $s, $a, %s2g, @glue, $glue, $delgs_or_auths, $wwn, $ww);
+ my (@s, $s, $a, %s2g, @glue, $glue, $delgs_or_auths, $wwn, $ww, $cg);
my ($rcode);
$ww= "[$uaddr] $wa";
verbose("checking delegation by $ww");
@glue= @{ $s2g{$s} };
if (!@glue) {
zone_warning("glueless NS $s", $ww)
- unless $glueless_ok || zone_style('~',!$needglue) ||
- grep { has_suffix_of($zone,".$_"); }
- @{ $cfg->{'conv_glueless'} };
+ unless $glueless_ok
+ or zone_style('~',!$needglue)
+ or grep { has_suffix_of($zone,".$_"); }
+ @{ $cfg->{'conv_glueless'} }
+ or ((grep { has_suffix_of($s,".$_"); }
+ @{ $cfg->{'indirect_glue'} }) and
+ !(grep { has_suffix_of($zone,".$_"); }
+ @{ $cfg->{'indirect_glue'} }));
+ foreach $cg (@{ $cfg->{'conv_glueless'} }) {
+ zone_warning("nameserver $s (glueless) in".
+ " serverless-glueless namespace area $cg", $ww)
+ if has_suffix_of(".$s",".$cg");
+ }
($rcode,@glue)= lookup($s,'a','0',"glueless NS from $ww");
foreach $a (@glue) {
$wwn= "glueless NS from $ww";
sub zone_ns_name ($$) {
my ($name,$ww) = @_;
- my ($cg);
$delg_to_us=1 if grep { $name eq $_ } @{ $cfg->{'self_ns'} };
- foreach $cg (@{ $cfg->{'conv_glueless'} }) {
- zone_warning("nameserver $name in serverless-glueless".
- " namespace area $cg",
- $ww)
- if has_suffix_of(".$name",".$cg");
- }
zone_warning("published server, as $name, but configured as stealth",
$ww)
if $cfg->{'s'} =~ m/u/ &&
sub zone_server_addr ($$$$$) {
my ($addr,$name,$ww,$wwq,$is_soa) = @_;
- debug_trace("zone_server_addr ".join '|',@_);
+ debug_trace("zone_server_addr ".join('|',@_));
$addr_is_ok{$addr}= "$name ($wwq)"
if $is_soa || $cfg->{'s'} =~ m/u/;
zone_warning("forbidden nameserver address [$addr] $name",$ww)
if grep { $_ eq $addr } @{ $cfg->{'forbid_addr'} };
+ zone_warning("forbidden server address for our slave [$addr] $name",$ww)
+ if $cfg->{'s'} =~ m/p/ and
+ grep { $_ eq $addr } @{ $cfg->{'forbid_slave'} };
my ($name_is_self, $addr_is_self);
$name_is_self= grep { $_ eq $name }
}
if (!$name_is_self && !$addr_is_self &&
$is_soa && $cfg->{'s'} =~ m/p/) {
- zone_warning("SOA ORIGIN $name is not us (".
+ zone_warning("SOA MNAME $name is not us (".
(join ' ', @{ $cfg->{'self_soa'} }).")", $ww);
}
$delg_to_us=1 if $addr_is_self && !$is_soa;
sub zone_check_soa ($$$$) {
my ($uaddr,$wa,$name,$is_ns) = @_;
- my ($lame,$serial,$origin,$got,$rcode,@soa_addrs,$soa_addr,$ww,$wwn);
+ my ($lame,$serial,$mname,$got,$rcode,@soa_addrs,$soa_addr,$ww,$wwn);
verbose("checking service at [$uaddr] $name");
$lame= 'dead or lame';
$ww= "[$uaddr] $wa";
if ($dig_type eq 'flags:') {
$lame= $dig_rdata =~ m/ aa / ? '' : 'lame';
} elsif ($dig_type eq 'soa' && $dig_owner eq $zone && !$lame) {
- die "several SOAs ? $ww" if defined $origin;
+ die "several SOAs ? $ww" if defined $mname;
$got= $dig_rdata;
$got =~ m/^(\d+) (\S+)$/ or die "$got ?";
- ($serial,$origin) = ($1,$2);
+ ($serial,$mname) = ($1,$2);
}
},
$zone,'soa',$uaddr);
- $lame= 'broken' if !$lame && !defined $origin;
+ $lame= 'broken' if !$lame && !defined $mname;
if ($lame) { zone_warning("$lame server [$uaddr]",$wa); return; }
progress(2, sprintf "%-16s %46s has %s%s",
$zone, "$name [$uaddr]", $serial, $is_ns ? '' : '*');
push @{ $soas{$got} }, $ww;
- ($rcode,@soa_addrs)= lookup($origin,'a','0',"SOA ORIGIN");
- $wwn= "SOA ORIGIN from $ww";
+ ($rcode,@soa_addrs)= lookup($mname,'a','0',"SOA MNAME");
+ $wwn= "SOA MNAME from $ww";
foreach $soa_addr (@soa_addrs) {
- zone_server_queue($soa_addr,$origin,$wwn,"SOA [$uaddr]",1);
+ zone_server_queue($soa_addr,$mname,$wwn,"SOA [$uaddr]",1);
}
}
sub zone_consistency() {
- my ($d, $org_ser, $origin, $a, $h, $self_soa, $wa);
+ my ($d, $org_ser, $mname, $a, $h, $self_soa, $wa);
zone_consistency_set('delegations',\%delgs);
foreach $d (keys %delgs) { delete $auths{$d}; }
zone_consistency_set('zone nameserver rrset',\%auths);
foreach $h (keys %glue) {
zone_consistency_set("glue for $h", $glue{$h});
}
- zone_consistency_set("serial number and/or SOA ORIGIN",\%soas);
+ zone_consistency_set("serial number and/or SOA MNAME",\%soas);
$self_soa= $cfg->{'self_soa'};
}
}
sub zone_servers_simplefind () {
- my ($rcode,@nsnames,$ns,@soas,$origin);
+ my ($rcode,@nsnames,$ns,@soas,$mname);
($rcode,@nsnames)= lookup($zone,'ns-','0',"zone's servers");
foreach $ns (@nsnames) {
}
$delgs{join ' ', sort @nsnames} = [ "zone's servers" ];
- ($rcode,@soas)= lookup($zone,'soa','0',"SOA ORIGIN");
+ ($rcode,@soas)= lookup($zone,'soa','0',"SOA MNAME");
die "multiple SOA RRs in set! @soas ?" if @soas!=1;
$soas[0] =~ m/^(\S+)\s/ or die "SOA ? $_";
zone_server_simple(domain_canon($1,"lookup $zone SOA"),'SOA',1);
my ($name,$ww,$is_soa) = @_;
my ($rcode,@addrs,$addr);
($rcode,@addrs)= lookup($name,'a','0', "server - ".
- ($is_soa ? "SOA ORIGIN" : "NS"));
+ ($is_soa ? "SOA MNAME" : "NS"));
foreach $addr (@addrs) { zone_server_addr($addr,$name,$ww,$ww,$is_soa); }
}
for (;;) {
$m_lock= new IO::File "${m_base}_lock", O_RDWR|O_CREAT, 0600
- or die "$quis: create lockfile ${m_base}_lock";
+ or die "$quis: create lockfile ${m_base}_lock: $!\n";
if (!flock($m_lock, LOCK_EX|LOCK_NB)) {
<$m_lock> =~ m/^\d+ /;
die "$quis: $zone: concurrrency? - flock $&$!\n";
$m_lock->close;
- next;
+ return 0;
}
- (@s1= $m_lock->fstat) or die "$quis: fstat ${m_base}_lock: $!\n";
+ (@s1= $m_lock->stat) or die "$quis: fstat ${m_base}_lock: $!\n";
(@s2= stat "${m_base}_lock") or
die "$quis: stat ${m_base}_lock: $!\n";
last if ($s1[0] eq $s2[0] && $s1[1] eq $s2[1]);
if ($2 eq ':' && $3 eq ':') {
warn "$quis: $zone: mid/last run, but last".
"run already done, ignoring zone\n";
- next;
+ return 0;
}
@m_ok= split /\:/, $2;
@m_fail= split /\:/, $3;
}
} elsif ($! != &ENOENT) {
die "$quis: open ${m_base}_info: $!\n";
- } elsif ($domail eq 'first') {
+ } elsif ($domail ne 'first') {
warn "$quis: $zone: first run, but not --mail-first,".
" ignoring zone\n";
- next;
+ return 0;
}
if ($domail eq 'first') {
remove "${m_base}_history" or $!==&ENOENT
$m_time= time;
progress(-1, "\n".('-'x70)."\n".ptime($m_time)."\n");
+ return 1;
}
sub mail_zone_after () {
progress(-1,"everything is fine");
}
close $progress_fh or die "$quis: close ${m_base}_history: $!\n";
- $progress_fh= $warn_fh= 'STDERR';
+ $progress_fh= $warn_fh= $stderr_fh;
if ($domail =~ m/^final/) {
if (100*@m_fail <= $$cfg{'mailmwarn'}*(@m_fail + @m_ok)) {
- printf " %-40s ok\n" or die "$quis: mail ok report: $!\n";
+ printf " %-40s ok\n", $zone or die "$quis: mail ok report: $!\n";
} elsif (zone_style('@',0)) {
- printf " %-40s mail suppressed\n"
+ printf " %-40s mail suppressed\n", $zone
or die "$quis: mail suppress report: $!\n";
} else {
mail_zone_mail();
}
- }
+ } else {
+ printf " %-40s %d warns. OK %s Fail %s\n",
+ $zone,
+ defined $zone_warnings{$zone} ? $zone_warnings{$zone} : 0,
+ join(',', map { $_ - $m_time } @m_ok),
+ join(',', map { $_ - $m_time } @m_fail)
+ or die "$quis: checking progress report: $!\n";
+ }
@m_fail= @m_ok= ('','')
if $domail =~ m/^final/;
}
sub mail_zone_mail () {
- my ($log, $zone_to, $zterr, $c, $r, $t);
+ my ($log, $zone_to, $zterr, $c, $r, $t, @soas);
$m_m= new IO::File "${m_base}_mail", 'w', 0666
or die "$quis: create ${m_base}_mail: $!\n";
$zone_to=''; $zterr='';
- if (!zone_style("\$",$$cfg{'s'} =~ m/u/)) {
+ if (!zone_style("\$", $$cfg{'s'} !~ m/[ps]/)) {
eval {
- $_= lookup($zone,'soa','0','problem-addr');
- m/\n\n/ and die "multiple soas\n";
- m/^\S+ (\S.*\@\S+) [0-9 ]+$/ or
+ ($r,@soas)= lookup($zone,'soa','0','problem-addr');
+ @soas==1 or die "multiple soas\n";
+ $soas[0] =~ m/^\S+ (\S.*\@\S+) [0-9 ]+$/ or
die "bad soa \`$_'\n";
$zone_to= $1;
};
$zterr =~ s/\n$//;
}
pmail <<END
-From: zone check system <$$cfg{'admin'}>
+From: zone checker <$$cfg{'admin'}>
Subject: $zone - configuration problems report
END
;
The zone has had configuration errors or persistent operational
problems during recent checks. See the logs below for details.
-Check history for $zone:
+Recent check history for $zone:
END
;
if ($m_lastok ne '-') {
pmail(" No record of this zone ever being fine.\n");
}
for $t (@m_fail) {
- pmail(" Errors/warnings at: ".ptime($t)."\n");
+ pmail(" Zone had problems at: ".ptime($t)."\n");
}
for $t (@m_ok) {
pmail(" Everything in order at: ".ptime($t)."\n");
$log= new IO::File "${m_base}_history",'r'
or die "$quis: reopen ${m_base}_history: $!";
undef $/;
- pmail($log);
+ pmail($log->getline);
$/= "\n";
- $log->error && $log->close
- or die "$quis: reread or close ${m_base}_log: $!";
+ (!$log->error and $log->close)
+ or die "$quis: reread or close ${m_base}_log: $!\n";
+ $log->eof or die;
$m_m->close or die "$quis: close ${m_base}_mail: $!\n";
$m_m= new IO::File "${m_base}_mail"
or die "$quis: reopen ${m_base}_mail: $!";
defined($c= fork) or die "$quis: fork for mail: $!\n";
if (!$c) {
- open STDIN, "<& ${m_m}" or die "$quis - sendmail: dup for stdin: $!\n";
- exec (qw(/usr/sbin/sendmail -odq -oee -oi),
+ defined dup2($m_m->fileno, 0)
+ or die "$quis - sendmail: dup for stdin: $!\n";
+ exec (qw(/usr/sbin/sendmail -odb -oee -oi),
($domail eq 'final' ? '-t' : $$cfg{'admin'}));
die "$quis - sendmail: exec: $!\n";
}
$r == $c or die "$quis: waitpid sendmail ($c): $r $!";
$? and warn "$quis: sendmail failed: $?\n";
- printf " %-40s %s\n", $zone, $zone_to or
- die "$quis: write mailing report: $!\n";
+ printf " %-40s %s\n", $zone,
+ length $zone_to ? $zone_to : 'reporting to admin'
+ or die "$quis: write mailing report: $!\n";
}
#-------------------- outputting