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