chiark / gitweb /
New style mechanism.
authorianmdlvl <ianmdlvl>
Sun, 26 May 2002 18:21:26 +0000 (18:21 +0000)
committerianmdlvl <ianmdlvl>
Sun, 26 May 2002 18:21:26 +0000 (18:21 +0000)
scripts/named-conf
scripts/named-conf.8

index 8dc922a5f156c655840f36bfa0f608c71ab09f77..d2f6b35cade95dfadd62742034dc6613df3530e4 100755 (executable)
@@ -1,5 +1,3 @@
-fixme implement --nothing by checking $mode='x';
-fixme implement ~
 fixme implement .* on self-soa self-ns
 
 #!/usr/bin/perl -w
 fixme implement .* on self-soa self-ns
 
 #!/usr/bin/perl -w
@@ -14,7 +12,8 @@ use vars qw($quis
            $mode $doall $domail
            $etcfile $where
            $debug $needglue $localonly $repeat $verbosity
            $mode $doall $domail
            $etcfile $where
            $debug $needglue $localonly $repeat $verbosity
-           $progress_fh);
+           $progress_fh $warn_fh $modifiers
+           %group2modcmd %group2used);
 
 $quis= $0; $quis =~ s,.*/,,;
 
 
 $quis= $0; $quis =~ s,.*/,,;
 
@@ -31,6 +30,7 @@ $repeat= 0;
 $domail= '';
 $progress_fh= 'STDOUT';
 $warn_fh= 'STDERR';
 $domail= '';
 $progress_fh= 'STDOUT';
 $warn_fh= 'STDERR';
+$modifiers= '';
 
 use vars qw($dig_owner $dig_type $dig_rdata);
 
 
 use vars qw($dig_owner $dig_type $dig_rdata);
 
@@ -59,6 +59,11 @@ while (@ARGV && $ARGV[0] =~ m/^\-/) {
            elsif (s/^D//) { $debug++; }
            elsif (s/^g//) { $needglue=0; }
            elsif (s/^l//) { $localonly=1; }
            elsif (s/^D//) { $debug++; }
            elsif (s/^g//) { $needglue=0; }
            elsif (s/^l//) { $localonly=1; }
+           elsif (s/^m(\w+)(\W+)$//) {
+               my ($g,$m) = ($1,$2);
+               $group2modcmd{$g}=$m;
+               usageerr("modifiers $m for group $g: $@") if bad_modifiers($m);
+           }
            elsif (s/^q//) { $verbosity--; }
            elsif (s/^r//) { $repeat=1; }
            elsif (s/^v//) { $verbosity++; }
            elsif (s/^q//) { $verbosity--; }
            elsif (s/^r//) { $repeat=1; }
            elsif (s/^v//) { $verbosity++; }
@@ -129,7 +134,7 @@ $default_output= '';
 %output_contents= ();
 
 use vars qw($check $install);
 %output_contents= ();
 
 use vars qw($check $install);
-$check= $mode !~ m/^f/;
+$check= $mode !~ m/^[fx]/;
 $install= $mode =~ m/^[yf]/;
 
 read_config($etcfile);
 $install= $mode =~ m/^[yf]/;
 
 read_config($etcfile);
@@ -146,7 +151,7 @@ sub cfg_fail ($) { die "$quis: $where:\n $_[0]\n"; }
 
 sub read_config ($) {
     my ($if) = @_;
 
 sub read_config ($) {
     my ($if) = @_;
-    my ($fh,$z,@self,$before,
+    my ($fh,$z,@self,$before,$group,
        $mod,$dir,$prefix,$suffix,$subfile,$lprefix,$lsuffix,$zf);
     local ($_);
 
        $mod,$dir,$prefix,$suffix,$subfile,$lprefix,$lsuffix,$zf);
     local ($_);
 
@@ -177,13 +182,15 @@ sub read_config ($) {
        } elsif (m/^forbid\-addr(?:\s+([0-9. \t]+))?/) {
            @forbid_addr= defined $1 ? split /\s+/, $1 : ();
        } elsif (m,^
        } elsif (m/^forbid\-addr(?:\s+([0-9. \t]+))?/) {
            @forbid_addr= defined $1 ? split /\s+/, $1 : ();
        } elsif (m,^
-                primary\-dir ([\@*?~]*)
+                primary\-dir (\W*)
                 \s+ (\S+)/([^/ \t]*)
                 (?: \s+ ([^/ \t]*) (?: (/.+) )?
                  )?
                 $,x) {
            ($mod, $dir, $prefix, $suffix, $subfile) =
                ($1,qualify($2),$3,$4,$5);
                 \s+ (\S+)/([^/ \t]*)
                 (?: \s+ ([^/ \t]*) (?: (/.+) )?
                  )?
                 $,x) {
            ($mod, $dir, $prefix, $suffix, $subfile) =
                ($1,qualify($2),$3,$4,$5);
+           cfg_fail("modifiers $mod for directory $dir: $@")
+               if bad_modifiers($mod);
            $suffix= '' if !defined $suffix;
            $subfile= '' if !defined $subfile;
            $suffix= '_db' if !length $suffix && !length $subfile;
            $suffix= '' if !defined $suffix;
            $subfile= '' if !defined $subfile;
            $suffix= '_db' if !length $suffix && !length $subfile;
@@ -205,12 +212,21 @@ sub read_config ($) {
                zone_conf($z,'primary','p',$mod,$zf);
            }
            closedir D or cfg_fail("close primary-dir $dir:\n $!");
                zone_conf($z,'primary','p',$mod,$zf);
            }
            closedir D or cfg_fail("close primary-dir $dir:\n $!");
-       } elsif (m/^primary([\@*?~]*)\s+(\S+)\s+(\S+)$/) {
+       } elsif (m/^primary(\W*)\s+(\S+)\s+(\S+)$/) {
            zone_conf($2,'primary','p',$1,qualify($3));
            zone_conf($2,'primary','p',$1,qualify($3));
-       } elsif (m/^published([\@*?~]*)\s+(\S+)\s+([0-9.\t]+)$/) {
+       } elsif (m/^published(\W*)\s+(\S+)\s+([0-9.\t]+)$/) {
            zone_conf($2,'published','s',$1,'',$3);
            zone_conf($2,'published','s',$1,'',$3);
-       } elsif (m/^stealth([\@*?~]*)\s+(\S+)\s+([0-9. \t]+)$/) {
+       } elsif (m/^stealth(\W*)\s+(\S+)\s+([0-9. \t]+)$/) {
            zone_conf($2,'stealth','u',$1,'',split /\s+/, $3);
            zone_conf($2,'stealth','u',$1,'',split /\s+/, $3);
+       } elsif (m/^modifiers\s+(\W+)(?:\s+(\w+))$/) {
+           ($mod,$group) = ($1,$2);
+           cfg_fail("modifiers $mod for group $group: $@")
+               if bad_modifiers($mod);
+           if (exists $group2modcmd{$group}) {
+               $mod= $group2modcmd{$group};
+               $group2used{$group}++;
+           }
+           $modifiers= $mod;
        } elsif (m/^slave\-dir\s+(\S+)(?:(?:\s+(\S+))\s+(\S+))?$/) {
            ($slave_dir, $slave_prefix, $slave_suffix) = (qualify($1),$2,$3);
            $slave_prefix='' if !defined $slave_prefix;
        } elsif (m/^slave\-dir\s+(\S+)(?:(?:\s+(\S+))\s+(\S+))?$/) {
            ($slave_dir, $slave_prefix, $slave_suffix) = (qualify($1),$2,$3);
            $slave_prefix='' if !defined $slave_prefix;
@@ -234,6 +250,11 @@ sub read_config ($) {
                     " \`$_'");
        }
     }
                     " \`$_'");
        }
     }
+    foreach $group (keys %group2modcmd) {
+       next if exists $group2used{$group);
+       cfg_fail("command line specifies modifier group $group".
+                " but missing in configuration file");
+    }
     $fh->close or cfg_fail("close config file $if:\n $!");
 }
 
     $fh->close or cfg_fail("close config file $if:\n $!");
 }
 
@@ -243,6 +264,19 @@ sub qualify ($) {
     return $i;
 }
 
     return $i;
 }
 
+sub bad_modifiers ($) {
+    local ($_) = @_;
+    if (!eval {
+       die "bad modifier $&" if m/[^!*\$\@~?]/;
+       die "repeated modifier $1" if m/(.).*$1/;
+       1;
+    }) {
+       $@ =~ s/\n//;
+       return 1;
+    }
+    return 0;
+}
+
 sub zone_conf ($$$$$@) {
     my ($zone,$style,$sabbr,$mod,$file,@servers) = @_;
     my ($sfx,$aref);
 sub zone_conf ($$$$$@) {
     my ($zone,$style,$sabbr,$mod,$file,@servers) = @_;
     my ($sfx,$aref);
@@ -259,7 +293,9 @@ sub zone_conf ($$$$$@) {
     $zone_cfg{$zone}{'where'}= $where;
     $zone_cfg{$zone}{'file'}= $file;
     $zone_cfg{$zone}{'style_p'}= $style.$mod;
     $zone_cfg{$zone}{'where'}= $where;
     $zone_cfg{$zone}{'file'}= $file;
     $zone_cfg{$zone}{'style_p'}= $style.$mod;
-    $zone_cfg{$zone}{'s'}= $sabbr.$mod; # p)rimary s)econdary u)npub f)oreign
+    $zone_cfg{$zone}{'s'}= "$sabbr $mod $modifiers";
+    # p)rimary s)econdary u)npub f)oreign
+    # followed by modifiers, first per-zone, then default
     $zone_cfg{$zone}{'servers'}= [ @servers ];
     if ($domail) {
        length $admin_email && length $mail_state_dir or
     $zone_cfg{$zone}{'servers'}= [ @servers ];
     if ($domail) {
        length $admin_email && length $mail_state_dir or
@@ -285,197 +321,6 @@ sub set_output($) {
     $output_contents{$output}= '';
 }
 
     $output_contents{$output}= '';
 }
 
-#-------------------- mailing
-
-use vars qw($m_base $m_lastok @m_ok @m_fail
-           $m_info $m_time);
-
-sub mail_zone_before () {
-    $m_base= $$cfg{'maildir'}.'/'.$zone;
-    $m_lastok= '-';
-    @m_ok= ();
-    @m_fail= ();
-
-    for (;;) {
-       $m_lock= IO::File "${m_base}_lock", O_RDWR|O_CREAT, 0600
-           or die "$quis: create lockfile ${m_base}_lock";
-       if (!flock($m_lock, LOCK_EX|LOCK_NB)) {
-           <$m_lock> =~ m/^\d+ /;
-           die "$quis: $zone: concurrrency? - flock $&$!\n";
-           $m_lock->close;
-           continue;
-       }
-       (@s1= $m_lock->fstat) 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]);
-       $m_lock->close;
-    }
-    print $m_lock "$$        \n" or $m_lock->flush
-       die "$quis: write pid to ${m_base}_lock: $!\n";
-
-    if ($m_info= IO::File "${m_base}_info", 'r') {
-       $!=0; $_= <INFO>;
-       $_ =~ m/\n/ or die "$quis: read ${m_base}_info: $!\n";
-       m/^\d+ (\d+|-) ([0-9:]*) ([0-9:]*) / or
-           die "$quis: ${m_base}_info malformed\n";
-       $m_lastok= $1;
-       if ($domail ne 'first') {
-           if ($2 eq ':' && $3 eq ':') {
-               warn "$quis: $zone: mid/last run, but last".
-                   "run already done, ignoring zone\n";
-               next;
-           }
-           @m_ok= split /\:/, $2;
-           @m_fail= split /\:/, $3;
-       }
-    } elsif ($! != &ENOENT) {
-       die "$quis: open ${m_base}_info: $!\n";
-    } elsif ($domail eq 'first') {
-       warn "$quis: $zone: first run, but not --mail-first,".
-           " ignoring zone\n";
-       next;
-    }
-    if ($domail eq 'first') {
-       remove "${m_base}_history" or $!==&ENOENT
-           or die "$quis: remove ${m_base}_history: $!\n";
-    }
-    $progress_fh= $warn_fh= new IO::File "${m_base}_history",'a',0666
-       or die "$quis: open ${m_base}_history: $!\n";
-    $m_info= new IO::File "${m_base}_info.tmp",'w',0666
-       or die "$quis: open ${m_base}_info.tmp: $!\n";
-
-    $m_time= time;
-    progress(-1, "\n".('-'x70)."\n".ptime($m_time)."\n");
-}
-
-sub mail_zone_after () {
-    if ($zone_warnings{$zone}) {
-       push @m_fail, $m_time;
-       progress(-1,"failed - $zone_warnings{$zone} warnings");
-    } else {
-       push @m_ok, $m_time;
-       $m_lastok= $m_time;
-       progress(-1,"everything is fine");
-    }
-    close $progress_fh or die "$quis: close ${m_base}_history: $!\n";
-    $progress_fh= $warn_fh= 'STDERR';
-
-    if ($domail eq 'final') {
-       if (100*@m_fail <= $$cfg{'mailmwarn'}*(@m_mail + @m_ok)) {
-           printf " %-40s ok\n" or die "$quis: mail ok report: $!\n";
-       } elsif ($$cfg{'s'} =~ m/\@.*\@/) {
-           printf " %-40s mail suppressed\n"
-               or die "$quis: mail suppress report: $!\n";
-       } else {
-           mail_zone_mail();
-       }
-    }      
-
-    @m_fail= @m_ok= ('','')
-       if $domail eq 'final';
-
-    printf $m_info "%s %s %s %s \n",
-        $m_time, $m_lastok, join(':',@m_ok), join(':',@m_fail)
-           or die "$quis: write new ${m_base}_info: $!\n";
-    $m_info->close or die "$quis: close new ${m_base}_info: $!\n";
-    rename "${m_base}_info.tmp", "${m_base}_info"
-       or die "$quis: install new ${m_base}_info: $!\n";
-    $m_lock->close;
-}
-    
-
-sub pmail ($) { print $m_m $_[0] or die "$quis: write ${m_base}_mail: $!\n"; }
-sub ptime ($) { my ($time)=@_; return gmtime($time)." GMT ($time)"; }
-
-sub mail_zone_mail () {
-    my ($log, $zone_to, $zterr, $c, $r);
-    $m_m= new IO::File, "${m_base}_mail", 'w', 0666
-       or die "$quis: create ${m_base}_mail: $!\n";
-    $zone_to=''; $zterr='';
-    if ($$cfg{'s'} !~ m/[\@u]/) {
-       eval {
-           $_= lookup($zone,'soa','0','problem-addr');
-           m/\n\n/ and die "multiple soas\n";
-           m/^\S+ (\S.*\@\S+) [0-9 ]+$/ or
-               die "bad soa \`$_'\n";
-           $zone_to= $1;
-       };
-       $zterr= $@;
-       $zterr =~ s/\n$/;
-    }
-    pmail <<END
-From: zone check system <$$cfg{'admin'}>
-Subject: $zone - configuration problems report
-END
-;
-    pmail("To: ");
-    pmail("SOA MNAME for $zone <$zone_to>\nCC: ")
-       if length($zone_to);
-    pmail($$cfg{'admin'}."\n\n");
-    pmail <<END
-You are receiving this mail because your email address is listed
-in the SOA (Start Of Authority) record for $zone
-in the DNS, which means you are supposedly the DNS administrator
-responsible for this zone.  This report is generated automatically
-on behalf of $$cfg{'admin'}, who
-is the administrator for a server for the zone.  Please contact
-them if you have any problem with this report.
-
-END
-if length $zone_to;
-    pmail <<END
-Sent to $$cfg{'admin'} since SOA MNAME unavailable:
-$zterr
-
-END
-if length $zterr;
-    pmail <<END
-The zone has had configuration errors or persistent operational
-problems during recent checks.  See the logs below for details.
-
-Check history for $zone:
-END
-;
-    if ($m_lastok ne '-') {
-       pmail(" Last seen to be fine: ".ptime($m_lastok)."\n");
-    } else {
-       pmail(" No record of this zone ever being fine.\n");
-    }
-    for $t (@m_fail) {
-       pmail(" Errors/warnings at: ".ptime($t)."\n");
-    }
-    for $t (@m_ok) {
-       pmail(" Everything in order at: ".ptime($t)."\n");
-    }
-    pmail("\n");
-    $log= new IO::File "${m_base}_history",'r'
-       or die "$quis: reopen ${m_base}_history: $!";
-    undef $/;
-    pmail($log);
-    $/= "\n";
-    $log->error && $log->close
-       or die "$quis: reread or close ${m_base}_log: $!";
-    $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 '/usr/sbin/sendmail','-odq','-oee','-oi',
-           'ijackson@chiark.greenend.org.uk'; # should be -t
-       die "$quis - sendmail: exec: $!\n";
-    }
-    $m_m->close;
-    $!=0; $r= waitpid $c,0;
-    $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";
-}
-
 #-------------------- checking
 
 use vars qw($zone $cfg $warnings %zone_warnings);
 #-------------------- checking
 
 use vars qw($zone $cfg $warnings %zone_warnings);
@@ -503,7 +348,7 @@ sub process_zones (@) {
        mail_zone_before() if $domail;
        zone_reset();
        progress(1, sprintf "%-20s %s", $zone, $$cfg{'style_p'});
        mail_zone_before() if $domail;
        zone_reset();
        progress(1, sprintf "%-20s %s", $zone, $$cfg{'style_p'});
-       if ($check && ($doall || $cfg->{'s'} !~ m/\?/)) {
+       if ($check && ($doall || !zone_style('?',0)) {
            eval {
                if ($localonly && $cfg->{'s'} =~ m/f/) {
                    zone_warning("foreign zone specified with -l",'');
            eval {
                if ($localonly && $cfg->{'s'} =~ m/f/) {
                    zone_warning("foreign zone specified with -l",'');
@@ -580,7 +425,7 @@ sub zone_investigate() {
        %nsrrset_checked, %soa_checked, $addr, $glueless_ok,
        $rcode, $name, $is_ns);
 
        %nsrrset_checked, %soa_checked, $addr, $glueless_ok,
        $rcode, $name, $is_ns);
 
-    if ($cfg->{'s'} !~ m/\*/) {
+    if (!zone_style('*',0)) {
        $super_zone= $zone;
        for (;;) {
            debug_trace("zone $zone superzone $super_zone");
        $super_zone= $zone;
        for (;;) {
            debug_trace("zone $zone superzone $super_zone");
@@ -654,7 +499,7 @@ sub zone_check_nsrrset ($$$$) {
        @glue= @{ $s2g{$s} };
        if (!@glue) {
            zone_warning("glueless NS $s", $ww)
        @glue= @{ $s2g{$s} };
        if (!@glue) {
            zone_warning("glueless NS $s", $ww)
-               unless $glueless_ok || !$needglue ||
+               unless $glueless_ok || zone_style('~',!$needglue) || 
                       grep { has_suffix_of($zone,".$_"); }
                           @{ $cfg->{'conv_glueless'} };
            ($rcode,@glue)= lookup($s,'a','0',"glueless NS from $ww");
                       grep { has_suffix_of($zone,".$_"); }
                           @{ $cfg->{'conv_glueless'} };
            ($rcode,@glue)= lookup($s,'a','0',"glueless NS from $ww");
@@ -828,6 +673,204 @@ sub zone_server_simple ($$$) {
     foreach $addr (@addrs) { zone_server_addr($addr,$name,$ww,$ww,$is_soa); }
 }
 
     foreach $addr (@addrs) { zone_server_addr($addr,$name,$ww,$ww,$is_soa); }
 }
 
+sub zone_style ($$) {
+    my ($stylechar, $default) = @_;
+    local ($_) = $$cfg{'s'};
+    $stylechar =~ s/\W/\\$&/ or die;
+    return m/(\S*)$stylechar/ ? $1 !~ m/\!/ : $default;
+}
+
+#-------------------- mailing
+
+use vars qw($m_base $m_lastok @m_ok @m_fail
+           $m_info $m_time);
+
+sub mail_zone_before () {
+    $m_base= $$cfg{'maildir'}.'/'.$zone;
+    $m_lastok= '-';
+    @m_ok= ();
+    @m_fail= ();
+
+    for (;;) {
+       $m_lock= IO::File "${m_base}_lock", O_RDWR|O_CREAT, 0600
+           or die "$quis: create lockfile ${m_base}_lock";
+       if (!flock($m_lock, LOCK_EX|LOCK_NB)) {
+           <$m_lock> =~ m/^\d+ /;
+           die "$quis: $zone: concurrrency? - flock $&$!\n";
+           $m_lock->close;
+           continue;
+       }
+       (@s1= $m_lock->fstat) 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]);
+       $m_lock->close;
+    }
+    print $m_lock "$$        \n" or $m_lock->flush
+       die "$quis: write pid to ${m_base}_lock: $!\n";
+
+    if ($m_info= IO::File "${m_base}_info", 'r') {
+       $!=0; $_= <INFO>;
+       $_ =~ m/\n/ or die "$quis: read ${m_base}_info: $!\n";
+       m/^\d+ (\d+|-) ([0-9:]*) ([0-9:]*) / or
+           die "$quis: ${m_base}_info malformed\n";
+       $m_lastok= $1;
+       if ($domail ne 'first') {
+           if ($2 eq ':' && $3 eq ':') {
+               warn "$quis: $zone: mid/last run, but last".
+                   "run already done, ignoring zone\n";
+               next;
+           }
+           @m_ok= split /\:/, $2;
+           @m_fail= split /\:/, $3;
+       }
+    } elsif ($! != &ENOENT) {
+       die "$quis: open ${m_base}_info: $!\n";
+    } elsif ($domail eq 'first') {
+       warn "$quis: $zone: first run, but not --mail-first,".
+           " ignoring zone\n";
+       next;
+    }
+    if ($domail eq 'first') {
+       remove "${m_base}_history" or $!==&ENOENT
+           or die "$quis: remove ${m_base}_history: $!\n";
+    }
+    $progress_fh= $warn_fh= new IO::File "${m_base}_history",'a',0666
+       or die "$quis: open ${m_base}_history: $!\n";
+    $m_info= new IO::File "${m_base}_info.tmp",'w',0666
+       or die "$quis: open ${m_base}_info.tmp: $!\n";
+
+    $m_time= time;
+    progress(-1, "\n".('-'x70)."\n".ptime($m_time)."\n");
+}
+
+sub mail_zone_after () {
+    if ($zone_warnings{$zone}) {
+       push @m_fail, $m_time;
+       progress(-1,"failed - $zone_warnings{$zone} warnings");
+    } else {
+       push @m_ok, $m_time;
+       $m_lastok= $m_time;
+       progress(-1,"everything is fine");
+    }
+    close $progress_fh or die "$quis: close ${m_base}_history: $!\n";
+    $progress_fh= $warn_fh= 'STDERR';
+
+    if ($domail eq 'final') {
+       if (100*@m_fail <= $$cfg{'mailmwarn'}*(@m_mail + @m_ok)) {
+           printf " %-40s ok\n" or die "$quis: mail ok report: $!\n";
+       } elsif (zone_style('@',0)) ) {
+           printf " %-40s mail suppressed\n"
+               or die "$quis: mail suppress report: $!\n";
+       } else {
+           mail_zone_mail();
+       }
+    }      
+
+    @m_fail= @m_ok= ('','')
+       if $domail eq 'final';
+
+    printf $m_info "%s %s %s %s \n",
+        $m_time, $m_lastok, join(':',@m_ok), join(':',@m_fail)
+           or die "$quis: write new ${m_base}_info: $!\n";
+    $m_info->close or die "$quis: close new ${m_base}_info: $!\n";
+    rename "${m_base}_info.tmp", "${m_base}_info"
+       or die "$quis: install new ${m_base}_info: $!\n";
+    $m_lock->close;
+}
+    
+
+sub pmail ($) { print $m_m $_[0] or die "$quis: write ${m_base}_mail: $!\n"; }
+sub ptime ($) { my ($time)=@_; return gmtime($time)." GMT ($time)"; }
+
+sub mail_zone_mail () {
+    my ($log, $zone_to, $zterr, $c, $r);
+    $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/)) {
+       eval {
+           $_= lookup($zone,'soa','0','problem-addr');
+           m/\n\n/ and die "multiple soas\n";
+           m/^\S+ (\S.*\@\S+) [0-9 ]+$/ or
+               die "bad soa \`$_'\n";
+           $zone_to= $1;
+       };
+       $zterr= $@;
+       $zterr =~ s/\n$/;
+    }
+    pmail <<END
+From: zone check system <$$cfg{'admin'}>
+Subject: $zone - configuration problems report
+END
+;
+    pmail("To: ");
+    pmail("SOA MNAME for $zone <$zone_to>\nCC: ")
+       if length($zone_to);
+    pmail($$cfg{'admin'}."\n\n");
+    pmail <<END
+You are receiving this mail because your email address is listed
+in the SOA (Start Of Authority) record for $zone
+in the DNS, which means you are supposedly the DNS administrator
+responsible for this zone.  This report is generated automatically
+on behalf of $$cfg{'admin'}, who
+is the administrator for a server for the zone.  Please contact
+them if you have any problem with this report.
+
+END
+if length $zone_to;
+    pmail <<END
+Sent to $$cfg{'admin'} since SOA MNAME unavailable:
+$zterr
+
+END
+if length $zterr;
+    pmail <<END
+The zone has had configuration errors or persistent operational
+problems during recent checks.  See the logs below for details.
+
+Check history for $zone:
+END
+;
+    if ($m_lastok ne '-') {
+       pmail(" Last seen to be fine: ".ptime($m_lastok)."\n");
+    } else {
+       pmail(" No record of this zone ever being fine.\n");
+    }
+    for $t (@m_fail) {
+       pmail(" Errors/warnings at: ".ptime($t)."\n");
+    }
+    for $t (@m_ok) {
+       pmail(" Everything in order at: ".ptime($t)."\n");
+    }
+    pmail("\n");
+    $log= new IO::File "${m_base}_history",'r'
+       or die "$quis: reopen ${m_base}_history: $!";
+    undef $/;
+    pmail($log);
+    $/= "\n";
+    $log->error && $log->close
+       or die "$quis: reread or close ${m_base}_log: $!";
+    $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 '/usr/sbin/sendmail','-odq','-oee','-oi',
+           'ijackson@chiark.greenend.org.uk'; # should be -t
+       die "$quis - sendmail: exec: $!\n";
+    }
+    $m_m->close;
+    $!=0; $r= waitpid $c,0;
+    $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";
+}
+
 #-------------------- outputting
 
 sub zone_output () {
 #-------------------- outputting
 
 sub zone_output () {
index 85d93982daaf29494fe48a41b45fe9e02c9d5b30..a14765cb9e4cb9b76aa05cea582498930ba860f0 100644 (file)
@@ -85,8 +85,10 @@ increase the debugging level.  (Maximum is
 .BR -DD .)
 .TP
 .BR \-g | \-\-glueless
 .BR -DD .)
 .TP
 .BR \-g | \-\-glueless
-Do not warn about glueless referrals.  Not recommended - see
-the section GLUELESSNESS, below.
+Do not warn about glueless referrals (strictly, makes the zone style
+modifier
+.B ~
+the default).  Not recommended - see the section GLUELESSNESS, below.
 .TP
 .BR \-l | \-\-local
 Only checks for mistakes which are the responsibility of the local
 .TP
 .BR \-l | \-\-local
 Only checks for mistakes which are the responsibility of the local
@@ -98,6 +100,20 @@ primary zones all checks are still done.  It is a mistake to specify
 with foreign zones (zones supplied explictly on the command line but
 not relevant to the local server); doing so produces a warning.
 .TP
 with foreign zones (zones supplied explictly on the command line but
 not relevant to the local server); doing so produces a warning.
 .TP
+.BI \-m group !*$@~?
+Overrides a
+.B modifiers
+directive in the configuration file.  The modifiers specified in the
+directive are completely replaced by those specified in this command
+line option.  (Note that modifiers specified in per-zone directives
+still override these per-group settings.)  If more than one
+.B modifiers
+directive specifies the same group, they are all affected.
+.B modifiers
+directives which don't specify a group cannot be affected.  It is an
+error if the group does not appear in the config file.  See ZONE STYLE
+MODIFIERS, below.
+.TP
 .BR \-q | \-\-quiet
 Suppress the usual report of the list of nameservers for each zone and
 the serial number from each.  When specified twice, do not print any
 .BR \-q | \-\-quiet
 Suppress the usual report of the list of nameservers for each zone and
 the serial number from each.  When specified twice, do not print any
@@ -176,6 +192,27 @@ had warnings or errors more than
 of the times \-\-mail\-* was used (since the last \-\-mail\-first).
 The default is 50%.
 .TP
 of the times \-\-mail\-* was used (since the last \-\-mail\-first).
 The default is 50%.
 .TP
+.BR modifiers " " !*$@~? "] [\fIgroup\fP]"
+Applies the specified zone style modifiers (see below) to subsequently
+declared zones (until the next
+.B modifiers
+directive), as if the modifiers specified were written out for
+each zone.  You must specify at least one character for the modifiers;
+if you want to reset everything to the default, just say
+.BR ! .
+If style modifiers specified in the zone directive
+conflict with the
+.B modifiers
+directive, those specified in the zone directive take effect.
+.I group
+may contain alphanumerics and underscores, and is used for the
+.B -m
+command-line option.
+.TP
+\fBself\-addr\fP \fIip-address ...\fP
+Specifies the list of addresses that this server may be known by in
+A records.  There is no default.
+.TP
 \fBoutput\fP \fIformat\fP \fIfilename\fP [\fIformat\fP \fIfilename ...\fP]
 Arranges that each
 .I filename
 \fBoutput\fP \fIformat\fP \fIfilename\fP [\fIformat\fP \fIfilename ...\fP]
 Arranges that each
 .I filename
@@ -198,10 +235,6 @@ configuration before the first
 .B output
 directive.
 .TP
 .B output
 directive.
 .TP
-\fBself\-addr\fP \fIip-address ...\fP
-Specifies the list of addresses that this server may be known by in
-A records.  There is no default.
-.TP
 \fBself\-ns\fP \fIfqdn ...\fP
 Specifies the list of names that this server may be known by in NS
 records.  There is no default.  Any trailing * is replaced by the name
 \fBself\-ns\fP \fIfqdn ...\fP
 Specifies the list of names that this server may be known by in NS
 records.  There is no default.  Any trailing * is replaced by the name
@@ -236,14 +269,14 @@ directive which does not specify them.
 .SS ZONE DIRECTIVES
 These directives specify one or more zones.
 .TP
 .SS ZONE DIRECTIVES
 These directives specify one or more zones.
 .TP
-.BR primary [ * | ? | @ | @@ | ~ "] \fIzone filename\fP"
+.BR primary [ !*$@~? "] \fIzone filename\fP"
 Specifies that this server is supposed to be the primary nameserver
 for
 .I zone
 and that the zone data is to be found in
 .IR filename .
 .TP
 Specifies that this server is supposed to be the primary nameserver
 for
 .I zone
 and that the zone data is to be found in
 .IR filename .
 .TP
-.BR primary\-dir [ * | ? | @ | @@ | ~ "] \fIdirectory\fP[" / "\fIprefix\fP] [\fIsuffix\fP[" / \fIsubfile\fP]]
+.BR primary\-dir [ !*$@~? "] \fIdirectory\fP[" / "\fIprefix\fP] [\fIsuffix\fP[" / \fIsubfile\fP]]
 Search
 .I directory
 for files whose names start with
 Search
 .I directory
 for files whose names start with
@@ -272,16 +305,27 @@ be the prefix.  If no
 is specified then the default is
 .BR _db .
 .TP
 is specified then the default is
 .BR _db .
 .TP
-.BR published [ * | ? | @ | @@ | ~ "] \fIzone origin\-addr\fP"
+.BR published [ !*$@~? "] \fIzone origin\-addr\fP"
 Specifies that this server is supposed to be a published slave
 nameserver for the zone in question.
 .TP
 Specifies that this server is supposed to be a published slave
 nameserver for the zone in question.
 .TP
-.BR stealth [ * | ? | @ | @@ | ~ "] \fIzone server\-addr ...\fP"
+.BR stealth [ !*$@~? "] \fIzone server\-addr ...\fP"
 Specifies that this server is supposed to be an unpublished secondary
 (aka stealth secondary) for the zone in question.
 Specifies that this server is supposed to be an unpublished secondary
 (aka stealth secondary) for the zone in question.
-.SS ZONE DIRECTIVE STYLE MODIFIERS
+.SS ZONE STYLE MODIFIERS
 Each of the zone directives may optionally be followed by one or more
 Each of the zone directives may optionally be followed by one or more
-of the following characters:
+of the following characters (each at most once):
+.TP
+.B !
+Reverses the meaning of all style modifiers after the
+.BR ! .
+Only one
+.BR !
+must appear in the modifier list.  In this list, other modifiers which
+default to `enabled' are described by describing the effect of their
+inverse - see the description for
+.B !@
+below.
 .TP
 .B *
 Indicates that the zone is unofficial, ie that it is not delegated as
 .TP
 .B *
 Indicates that the zone is unofficial, ie that it is not delegated as
@@ -291,12 +335,12 @@ zones should be created with caution.  They should be in parts of the
 namespace which are reserved for private use, or belong to the actual
 zone maintainer.
 .TP
 namespace which are reserved for private use, or belong to the actual
 zone maintainer.
 .TP
-.B @
-Indicates that mails should be sent about the zone to the nameserver
-admin rather than to the zone SOA MNAME.  This is always done for
-stealth zones.
+.B $
+Indicates that any mails should be sent about the zone to the
+nameserver admin rather than to the zone SOA MNAME.  This is the
+default for stealth zones.
 .TP
 .TP
-.B @@
+.B !@
 Indicates that no mails should be sent about the zone to anyone.
 .TP
 .B ~
 Indicates that no mails should be sent about the zone to anyone.
 .TP
 .B ~