chiark / gitweb /
Sort of working?
[chiark-utils.git] / scripts / named-conf
1 #!/usr/bin/perl -w
2
3 use strict;
4 use IO::File;
5 use Data::Dumper;
6
7 use vars qw($etcfile $where);
8 $etcfile= "/etc/bind/chiark-conf-gen.zones";
9 $where= '<built-in>';
10
11 use vars qw($mode $verbosity $debug);
12 $mode= '';
13 $verbosity= 1;
14 $debug= 0;
15
16 use vars qw($dig_owner $dig_type $dig_rdata);
17
18 while (@ARGV && $ARGV[0] =~ m/^\-/) {
19     $_= shift @ARGV;
20     if (s/^\-\-//) {
21         last if m/^$/;
22         if (m/^quiet$/) { $verbosity=0; }
23         elsif (m/^verbose$/) { $verbosity=2; }
24         elsif (m/^(yes|no|force)$/) { m/^./; $mode= $&; }
25         elsif (m/^config$/) { $etcfile= loarg(); $where= '--config option'; }
26         else { usageerr("unknown option --$_"); }
27     } else {
28         s/^\-//;
29         last if m/^$/;
30         while (m/^./) {
31             if (s/^[ynf]//) { $mode=$&; }
32             elsif (s/^v//) { $verbosity=2; }
33             elsif (s/^q//) { $verbosity=0; }
34             elsif (s/^D//) { $debug++; }
35             elsif (s/^C//) { $etcfile= soarg(); $where= '-C option'; }
36             else { usageerr("unknown option -$&"); }
37         }
38     }
39 }
40
41 sub loarg() { usageerr("missing option value") if !@ARGV; return shift @ARGV; }
42 sub soarg() { my ($rv); $rv=$_; $_=''; return length $rv ? $rv : loarg(); }
43
44 usageerr("must specify either -f|-y|-n or zones (and not both)")
45     if !!$mode == !!@ARGV;
46
47 sub usageerr ($) {
48     die
49 "$_[0]
50 usage: named-conf-regen [-rvq] -f|-y|-n|<zone>...\n".
51 "operation modes:\n".
52 " -f --force   install without checking\n".
53 " -y --yes     check and install\n".
54 " -n --no      check only\n".
55 "additional options:\n".
56 " -q --quiet   no output for OK zones\n".
57 " -v --verbose extra verbose\n";
58 }
59
60 cfg_fail("config filename $etcfile should have been absolute path of a file")
61     unless $etcfile =~ m,^/, && $etcfile !~ m,/$,;
62
63 use vars qw($default_dir);
64 $default_dir= $etcfile;
65 $default_dir =~ s,/[^/]+$,,;
66
67 use vars qw($slave_dir $slave_prefix $slave_suffix);
68 $slave_dir= 'slave';
69 $slave_prefix= '';
70 $slave_suffix= '';
71
72 use vars qw(@self_ns @self_soa);
73 @self_ns= @self_soa= ();
74
75 use vars qw(%zone_cfg @zone_cfg_list);
76 %zone_cfg= ();
77 @zone_cfg_list= ();
78
79 use vars qw($output $default_output %output_contents);
80 $output= '';
81 $default_output= '';
82 %output_contents= ();
83
84 use vars qw($check $install);
85 $check= $mode !~ m/^f/;
86 $install= $mode =~ m/^[yf]/;
87
88 read_config($etcfile);
89 debug_dump('@zone_cfg_list %zone_cfg');
90 process_zones($mode ? @zone_cfg_list : @ARGV);
91 debug_dump('%output_contents');
92
93
94 #-------------------- configuration reading
95
96 sub cfg_fail ($) { die "$0: $where:\n $_[0]\n"; }
97
98 sub read_config ($) {
99     my ($if) = @_;
100     my ($fh,$z,@self, $dir,$prefix,$suffix,$lprefix,$lsuffix);
101     local ($_);
102
103     $fh= new IO::File $if,'r' or cfg_fail("open $if:\n $!");
104     for (;;) {
105         if (!defined($_= <$fh>)) {
106             cfg_fail("read config file $if:\n $!") if $fh->error();
107             last;
108         }
109         $where= "$if:$.";
110         s/^\s+//; chomp; s/\s+$//;
111         next if m/^\#/;
112         last if m/^end$/;
113         next unless m/\S/;
114         if (m/^self(\-ns|\-soa|)\s+(\S.*\S)/) {
115             @self= split /\s+/, $2;
116             @self_ns= @self if $1 ne '-soa';
117             @self_soa= @self if $1 ne '-ns';
118         } elsif (m/^primary\-dir\s+(\S+)((?:\s+(\S+))??:\s+(\S+))?$/) {
119             ($dir, $prefix, $suffix) = (qualify($1),$2,$3);
120             $suffix= '_db' if !length $suffix;
121             opendir D, $dir or cfg_fail("open primary-dir $dir:\n $!");
122             $lprefix= length $prefix; $lsuffix= length $suffix;
123             while ($!=0, $_= readdir D) {
124                 next if m/^\./ && !$lprefix;
125                 next unless length > $lprefix+$lsuffix;
126                 next unless substr($_,0,$lprefix) eq $prefix;
127                 next unless substr($_,length($_)-$lsuffix) eq $suffix;
128                 $z= substr($_,$lprefix,length($_)-($lprefix+$lsuffix));
129                 zone_conf($z,'primary',"$dir/$_");
130             }
131             $! and cfg_fail("read primary-dir $dir:\n $!");
132             closedir D or cfg_fail("close primary-dir $dir:\n $!");
133         } elsif (m/^primary\s+(\S+)\s+(\S+)$/) {
134             zone_conf($1,'primary',qualify($2));
135         } elsif (m/^secondary\s+(\S+)\s+([0-9.\t]+)$/) {
136             zone_conf($1,'secondary','',$2);
137         } elsif (m/^stealth\s+(\S+)\s+([0-9. \t]+)$/) {
138             zone_conf($1,'stealth','',split /\s+/, $2);
139         } elsif (m/^slave\-dir\s+(\S+)((?:\s+(\S+))??:\s+(\S+))?$/) {
140             ($slave_dir, $slave_prefix, $slave_suffix) = (qualify($1),$2,$3);
141         } elsif (m/^output\s+bind8\+(\S+)$/) {
142             cfg_fail("default output may not apply to only some zones")
143                 if @zone_cfg_list && length $default_output;
144             set_output(qualify($1));
145         } elsif (m/^include\s+(\S+)$/) {
146             read_config($1);
147         } else {
148             cfg_fail("unknown configuration directive".
149                      " or incorrect syntax or arguments");
150         }
151     }
152     $fh->close or cfg_fail("close config file $if:\n $!");
153 }
154
155 sub qualify ($) {
156     my ($i) = @_;
157     $i= "$default_dir/$i" unless $i =~ m,^/,;
158 }
159
160 sub zone_conf ($$@) {
161     my ($zone,$style,$file,@servers) = @_;
162     $file= qualify("$slave_dir/$slave_prefix".$zone.$slave_suffix)
163         unless length $file;
164     if (!length $output) {
165         $default_output= qualify('chiark-conf-gen.bind8')
166             unless length $default_output;
167         set_output($default_output);
168     }
169     cfg_fail("redefined zone $zone") if exists $zone_cfg{$zone};
170     $zone_cfg{$zone}{'file'}= $file;
171     $zone_cfg{$zone}{'style'}= $style;
172     $zone_cfg{$zone}{'servers'}= [ @servers ];
173     $zone_cfg{$zone}{'self_soa'}= [ @self_soa ];
174     $zone_cfg{$zone}{'self_ns'}= [ @self_ns ];
175     $zone_cfg{$zone}{'output'}= $output;
176     push @zone_cfg_list, $zone;
177 }
178
179 sub set_output($) {
180     my ($newout) = @_;
181     $output= $newout;
182     $output_contents{$output}= '';
183 }
184
185
186 #-------------------- checking
187
188 use vars qw($zone $cfg $warnings);
189 $warnings= 0;
190
191 sub progress ($) {
192     return if !$verbosity;
193     print "$_[0]\n";
194 }
195
196 sub verbose ($) {
197     return if $verbosity<2;
198     print "    $_[0]\n";
199 }
200
201 sub process_zones (@) {
202     my (@zones) = @_;
203     local ($zone,$cfg);
204
205     foreach $zone (@zones) {
206         $cfg= $zone_cfg{$zone} || {
207             'style' => 'foreign',
208             'servers' => [ ],
209             };
210         progress(sprintf "%-40s %s", $zone, $$cfg{'style'});
211         if ($check) {
212             eval { zone_check() };
213             zone_warning("checks failed: $@") if length $@;
214         }
215         zone_output() if $install;
216     }
217 }
218
219 sub zone_warning ($) {
220     my ($w) = @_;
221     $w =~ s/\n$//;
222     $w =~ s,\n, // ,g;
223     print STDERR "$zone: warning: $w\n" or die $!;
224     $warnings++;
225 }
226
227 sub zone_warnmore ($) {
228     print STDERR " $_[0]\n" or die $!;
229 }
230
231 use vars qw(%delgs); # $delgs{$nameserver_list} = [ $whosaidandwhy ]
232 use vars qw(%auths); # $auths{$nameserver_list} = [ $whosaidandwhy ]
233 use vars qw(%glue);  # $glue{$name}{$addr_list} = [ $whosaidandwhy ]
234 use vars qw(%soas);  # $soa{"$origin $serial"} = [ $whosaidandwhy ]
235 use vars qw(%addr_is_ok);
236 use vars qw(@to_check); # ($addr,$whyask,$is_auth,$glueless_ok, ...)
237 use vars qw(@to_check_soa); # ($addr,$whyask, ...)
238
239 sub zone_check () {
240     my ($super_zone, @super_nsnames,
241         $super_ns, @super_ns_addrs, $s, $wa, $is_auth,
242         %nsrrset_checked, %soa_checked, $addr, $glueless_ok, $rcode);
243
244     %delgs= %auths= %glue= %soas= %addr_is_ok= ();
245     @to_check= @to_check_soa= ();
246
247     $super_zone= $zone;
248     for (;;) {
249         debug_trace("zone $zone superzone $super_zone");
250         $super_zone =~ s/^[^.]+\.// or die "no superzone ? ($super_zone)\n";
251         ($rcode,@super_nsnames)= lookup($super_zone,'ns-','06');
252         last if !$rcode;
253     }
254     for $super_ns (@super_nsnames) {
255         $super_ns= lc $super_ns;
256         ($rcode,@super_ns_addrs)= lookup($super_ns,'a','0');
257         foreach $addr (@super_ns_addrs) {
258             push @to_check,
259                  $addr,
260                  "$super_ns, server for $super_zone",
261                  0, 0;
262         }
263     }
264     for (;;) {
265         # We do these in order so that we always do NS RRset checks on
266         # nameservers that came from other NS RRsets first; otherwise
267         # we might set nsrrset_checked due to a glueless_ok check,
268         # and then not check for gluefulness later.
269         debug_dump('@to_check @to_check_soa');
270         if (($addr,$wa,$is_auth,$glueless_ok,@to_check) = @to_check) {
271             push @to_check_soa, $addr, $wa if $is_auth;
272             next if $nsrrset_checked{$addr}++;
273             zone_check_nsrrset($addr, "[$addr] $wa",
274                                $is_auth, $glueless_ok);
275         } elsif (($addr,$wa,@to_check_soa) = @to_check_soa) {
276             next if $soa_checked{$addr}++;
277             zone_check_soa($addr,"[$addr] $wa");
278         } else {
279             last;
280         }
281     }
282     zone_consistency();
283 }
284
285 sub zone_check_nsrrset ($$$$) {
286     my ($uaddr,$ww, $is_auth, $glueless_ok) = @_;
287     my (@s, $s, %s2g, @glue, $glue, $delgs_or_auths);
288     verbose("checking delegation by $ww");
289     dig(sub {
290         if ($dig_type eq 'ns' && $dig_owner eq $zone) {
291             $s2g{lc $dig_rdata} = [ ];
292         } elsif ($dig_type eq 'a' && exists $s2g{$dig_owner}) {
293             push @to_check,
294                  $dig_rdata,
295                  "$dig_owner, in glue from $ww",
296                  1, 0;
297             $addr_is_ok{$dig_rdata}= "$dig_owner (NS [$uaddr])"
298                 if $cfg->{'style'} eq 'stealth';
299             push @{ $s2g{$dig_owner} }, $dig_rdata;
300         }
301     },
302              $zone,'ns',$uaddr);
303     if (!%s2g) { zone_warning("unable to find NS RRset at $ww"); return; }
304     elsif (keys %s2g == 1) { zone_warning("only one nameserver at $ww"); }
305     @s= sort keys %s2g;
306     foreach $s (@s) {
307         @glue= @{ $s2g{$s} };
308         if (!@glue) {
309             zone_warning("glueless NS $s, from $ww") unless $glueless_ok;
310             next;
311         }
312         $glue= join ' ', sort @glue;
313         push @{ $glue{$s}{$glue} }, $ww;
314     }
315     $s= join ' ', @s;
316     $delgs_or_auths= $is_auth ? \%auths : \%delgs;
317     push @{ $delgs_or_auths->{$s} }, $ww;
318 }
319
320 sub zone_check_soa ($$) {
321     my ($uaddr,$ww) = @_;
322     my ($lame,$origin,$got,$rcode,@soa_addrs,$soa_addr);
323     verbose("checking service at $ww");
324     $lame= 'dead or lame';
325     dig(sub {
326         if ($dig_type eq 'flags:') {
327             $lame= $dig_rdata =~ m/ aa / ? '' : 'lame';
328         } elsif ($dig_type eq 'soa' && $dig_owner eq $zone && !$lame) {
329             die "several SOAs ? $ww" if defined $origin;
330             $got= $dig_rdata;
331             $got =~ m/^(\S+) \d+/ or die "$got ?";
332             $origin= $1;
333         }
334     },
335              $zone,'soa',$uaddr);
336     $lame= 'broken' if !$lame && !defined $origin;
337     if ($lame) { zone_warning("$lame server $ww"); return; }
338     push @{ $soas{$got} }, $ww;
339     ($rcode,@soa_addrs)= lookup($origin,'a','0');
340     foreach $soa_addr (@soa_addrs) {
341         $addr_is_ok{$soa_addr}= "$origin (SOA [$uaddr])";
342         push @to_check,
343              $soa_addr,
344              "$origin, SOA ORIGIN from $ww";
345     }
346 }
347
348 sub zone_consistency() {
349     my ($d, $org_ser, $origin, $a, $showok, $h);
350     zone_consistency_set('delegations',\%delgs);
351     foreach $d (keys %delgs) { delete $auths{$d}; }
352     zone_consistency_set('zone nameserver rrset',\%auths);
353     foreach $h (keys %glue) {
354         zone_consistency_set("glue for $h", $glue{$h});
355     }
356     zone_consistency_set("SOA ORIGIN and SERIAL",\%soas);
357     if ($cfg->{'style'} eq 'primary') {
358         foreach $org_ser (keys %soas) {
359             $org_ser =~ m/^(\S+) \d+$/ or die "$org_ser ?";
360             $origin= $1;
361             next if grep { $_ eq $origin } @self_soa;
362             zone_warning("our name(s) @self_soa not in SOA ORIGIN $origin,".
363                          " eg from ".((values %{ $soas{$org_ser} })[1]));
364         }
365     }
366     if (%addr_is_ok) {
367         $showok= 0;
368         foreach $a (@{ $cfg->{'servers'} }) {
369             next if exists $addr_is_ok{$a};
370             zone_warning("we slave from $a"); $showok=1;
371         }
372         if ($showok) {
373             foreach $a (keys %addr_is_ok) {
374                 zone_warnmore("permitted master [$a] $addr_is_ok{$a}");
375             }
376         }
377     }
378 }
379
380 sub zone_consistency_set ($%) {
381     my ($msg,$set) = @_;
382     my ($d,$o);
383     if (keys(%$set) > 1) {
384         zone_warning("inconsistent $msg:");
385         foreach $d (keys %$set) {
386             foreach $o (@{ $set->{$d} }) { zone_warnmore(" $d from $o"); }
387         }
388     }
389 }
390
391
392 #-------------------- outputting
393
394 sub zone_output () {
395     $output_contents{$$cfg{'output'}}.=
396         sprintf(<<'END',
397 zone "%s" {
398     type %s;
399     file "%s";
400 };
401 END
402                 $zone,
403                 $$cfg{'style'} eq 'primary' ? 'master' : 'slave',
404                 $$cfg{'file'});
405 }
406
407
408 #-------------------- general utilities
409
410 sub debug_dump ($) {
411     my ($vn);
412     return unless $debug>1;
413     local $Data::Dumper::Terse=1;
414     foreach $vn (split /\s+/, $_[0]) {
415         print "$vn := ", eval "Dumper(\\$vn)";
416     }
417 }
418
419 sub debug_trace ($) {
420     return unless $debug;
421     print "D $_[0]\n";
422 }
423
424 sub lookup ($$$) {
425     my ($domain,$type,$okrcodes) = @_;
426     my ($c,$h,@result);
427     debug_trace("lookup ==> (->$okrcodes) $domain $type");
428     $h= new IO::Handle;
429
430     defined($c= open $h, "-|") or die "$0: fork adnshost:\n $!\n";
431     if (!$c) {
432         exec 'adnshost','-Fi','+Do','+Dt','+Dc','-Cf',"-t$type",
433              '-',"$domain.";
434         die "$0: exec adnshost:\n $!\n";
435     }
436     @result= $h->getlines();
437     $h->error and die "$0: read from adnshost:\n $!\n";
438     chomp @result;
439     $!=0; $h->close;
440     die "$0: lookup -t$type $domain $okrcodes failed $? $!\n"
441         if $! or $?>6 or index($okrcodes,$?)<0;
442     debug_trace("lookup <== $? @result");
443     return ($?,@result);
444 }
445
446
447 sub dig (&$$$) {
448     my ($eachrr, $qowner,$qtype,$qaddr) = @_;
449     # also pseudo-rr with type `flags:'
450     my ($h,$inmid,$irdata,$c);
451     local ($_);
452
453     debug_trace("dig ==> \@$qaddr $qowner $qtype");
454
455     $h= new IO::Handle;
456     defined($c= open $h, "-|") or die "$0: fork dig:\n $!\n";
457     if (!$c) {
458         open STDERR, ">&STDOUT" or die $!;
459         exec ('dig',
460               '+nodef','+nosea','+nodebug','+norecurse',
461               "\@$qaddr",'-t',$qtype,$qowner);
462         die "$0: exec dig:\n $!\n";
463     }
464     $inmid='';
465     for (;;) {
466         if (!defined($_= $h->getline())) {
467             $h->error() and die "$0: read from dig:\n $!\n";
468             last;
469         }
470         chomp;
471         if (length $inmid) {
472             s/^\s+/ / or die "$inmid // $_ ?";
473             s/\;.*$//;
474             $_= $inmid.$_;
475             $inmid='';
476             s/$/ \(/ unless s/\s*\)\s*$//;
477         }
478         if (s/\s*\(\s*$//) { $inmid= $_; next; }
479         if (m/^\;\; flags\:( [-0-9a-z ]+)\;/) {
480             $dig_owner=''; $dig_type='flags:'; $dig_rdata= "$1 ";
481             debug_trace("dig  f: $dig_rdata");
482             &$eachrr;
483         } elsif (m/^\;/) {
484         } elsif (!m/\S/) {
485         } elsif (m/^([-.0-9a-z]+)\s+\d\w+\s+in\s+([a-z]+)\s+(\S.*)/i) {
486             $dig_owner=domain_canon($1); $dig_type=lc $2; $irdata=$3;
487             if ($dig_type eq 'a') {
488                 $irdata =~ m/^[.0-9]+$/ or die "$irdata ?";
489                 $dig_rdata= $&;
490             } elsif ($dig_type eq 'ns') {
491                 $irdata =~ m/^[-.0-9a-z]+$/i or die "bad nameserver $irdata ?";
492                 $dig_rdata= domain_canon($irdata);
493             } elsif ($dig_type eq 'soa') {
494                 $irdata =~ m/^([-.0-9a-z]+)\s+.*\s+(\d+)(?:\s+\d\w+){4}$/i
495                     or die "bad SOA $irdata ?";
496                 $dig_rdata= domain_canon($1).' '.$2;
497             } else {
498                 debug_trace("ignoring uknown RR type $dig_type");
499                 next;
500             }
501             debug_trace("dig  $dig_owner $dig_type $dig_rdata");
502             &$eachrr;
503         } else {
504             debug_trace("ignoring unknown dig output $_");
505         }
506     }
507     $h->close;
508     debug_trace("dig <== gave $?");
509 }
510
511 sub domain_canon ($) {
512     local ($_) = @_;
513     s/(.)\.$/$1/;
514     return lc $_;
515 }
516
517 __DATA__
518
519
520
521
522 sub lookup1 ($$) {
523     my ($type,$domain) = @_;
524     my (@result)= lookup($type,$domain);
525     @result==1 or die "$0: lookup -t$type $domain gave more than one RR\n";
526     return $result[0];
527 }
528
529 sub check () {
530     return unless $check;
531     eval {
532         $soa= lookup1('soa',$zone);
533         $soa_origin=$soa; $soa_origin =~ s/ .*//;
534         $soa_origin_addr= lookup1('a',$soa_origin);
535
536         @zone_ns= lookup('ns-',$zone);
537
538         @ok_sources= ($soa_origin_addr);
539         $ok_sources_descr= "SOA ORIGIN $soa_origin [$soa_origin_addr]";
540
541         if ($style eq 'unoff' || $style eq 'backup') {
542             for $zone_ns (@zone_ns) {
543                 @zone_ns_addrs= lookup('a',$zone_ns);
544                 push @ok_sources, @zone_ns_addrs;
545                 $ok_sources_descr.= ", NS $zone_ns [@zone_ns_addrs]";
546             }
547         }
548
549         for $server (@servers) {
550             grep { $server eq $_ } @ok_sources
551                 or warn "secondarying from $server which is not ".
552                         "$ok_sources_desc\n";
553         }
554
555         if ($style eq 'secondary') {
556             grep { $zone_ns=$_, grep { $myname eq $_ } @mynames } @zone_n
557                 or warn "supposedly published secondary but we ".
558                         "(@mynames) are not published ($@zone_ns)\n";
559         }
560     }
561     check_after_eval();
562     
563
564                          
565 #       $superzone= $zone; $superzone =~ s/^[^\.]+\.//;
566 #       @super_ns= lookup('ns-',$zone);
567     }
568
569     eval {
570
571         for $super_ns (@super_ns) {
572             @deleg_ns= ();
573             open DIG, "dig @$super_ns. -t ns +norecurse $zone."
574                 or die "$0: fork for dig:\n $!\n";
575             while (<DIG>) {
576                 
577
578             split /\n/, lookup("
579         
580                 case "$style" in
581                 secondary|backup)
582                         if [ $meadvert = 0 ]
583                         then
584                                 warning "$myname unlisted NS $nsnames"
585                         fi
586                         ;;
587                 unoff)
588                         if $meadvert = 0
589                         then
590                                 warning "$myname advertised NS $nsnames"
591                         fi
592                         ;;
593                 esac
594
595                 addrs=''
596                 for ns in $names
597                 do
598                         set -e; a="`host -t a \"$ns\".`"; set +e
599                         taddrs="`echo \" $a\" | expand | sed -n '
600                                  1s/^ //
601                                 s/^[^ ][^ ]*  *A  *\([0-9][.0-9]*\)/\1/p
602                         '`"
603                         equlines "A $ns" "$a" "$taddrs"
604                         addrs="$addrs $taddrs"
605                 done
606         fi
607
608         cat <<END
609 zone "$zone" {
610         type slave;
611         file "$file";
612         masters {
613 END
614         for server in $servers
615         do
616                 echo "          $server;"
617                 if $check
618                 then
619                         notfound=1
620                         for addr in $addrs
621                         do
622                                 if [ "x$addr" = "x$server" ]
623                                 then
624                                         notfound=0
625                                 fi
626                         done
627                         if $notfound
628                         then
629  warning "server $server? but $rectype $names" $addrs
630                         fi
631                 fi
632         done
633         cat <<END
634         };
635 };
636 END
637         hostfirstwarn=1
638         if $hostdelg
639         then
640                 checkhostout -C
641         fi
642         if $hostzone
643         then
644                 checkhostout -val localhost
645         fi
646 done
647 endfile
648
649     
650 chdir "$base/primary" or die "$0: chdir $base/primary:\n $!";
651 beginfile('primary.zones');
652
653 for $f (<*_db>) {
654     $zone= $f; $zone =~ s/_db$//;
655     
656
657 for f in $zones
658 do
659         zone="`echo $f | sed -e 's/_db$//'`"
660 END
661 done
662 endfile
663
664
665     
666 sub beginfile ($) {
667     $currentfile= $_[0];
668     $currentfile_opened= $install ? "$conf/$currentfile.new" : "/dev/null";
669     open CFF, "> $toopen" or die "$0: begin $currentfile_opened:\n $!\n";
670 }
671
672 endfile () {
673     close CFF or die "$0: close $currentfile_opened:\n $!\n";
674     push @files, $currentfile;
675 }
676
677 sub installfiles () {
678     return unless $install;
679     chdir $conf or die "$0: chdir $conf:\n $!\n";
680     for $f (@files) {
681         rename "$f.new", $f or die "$0: install new $f:\n $!\n";
682     }
683 }
684
685 warning () {
686         echo >&2 "$zone $style: $*"
687         warnings=$[$warnings+1]
688 }
689
690 equlines () {
691         if [ "x`echo \" $2\" | wc -l`" != "x`echo \" $3\" | wc -l`" ]
692         then
693                 warning "$1 >$2|$3<"
694         fi
695 }
696
697 checkhostout () {
698         set +e
699         hostout="`host $1 \"$zone\" 2>&1 >/dev/null $2 | egrep -v \
700 '^ \!\!\! .* SOA primary .* is not advertised via NS$'`"
701         set -e
702         if [ "x$hostout" = x ]; then return; fi
703         if $hostfirstwarn
704         then
705                 warning "warnings from host:"
706                 hostfirstwarn=0
707         fi
708         echo >&2 "$hostout"
709 }
710
711 progress () {
712         if $progress
713         then
714                 echo -n "$zone $style                    " >&2
715                 echo -ne '\r' >&2
716         fi
717 }
718
719 myname=''
720
721 if [ $warnings != 0 ]
722 then
723         echo >&2 "$warnings warnings                                        "
724 fi
725
726 installfiles