File Coverage

blib/lib/BIND/Conf_Parser.pm
Criterion Covered Total %
statement 27 523 5.1
branch 0 236 0.0
condition 0 36 0.0
subroutine 9 55 16.3
pod 14 46 30.4
total 50 896 5.5


line stmt bran cond sub pod time code
1             #
2             # BIND::Conf_Parser - Parser class for BIND configuration files
3             #
4             package BIND::Conf_Parser;
5              
6 1     1   741 use Carp;
  1         2  
  1         86  
7              
8 1     1   5 use strict;
  1         2  
  1         28  
9 1     1   896 use integer;
  1         14  
  1         5  
10 1     1   29 use vars qw($VERSION);
  1         2  
  1         58  
11              
12             $VERSION = "0.95";
13              
14             # token classes
15 1     1   6 use constant WORD => 'W';
  1         2  
  1         74  
16 1     1   5 use constant STRING => '"';
  1         1  
  1         39  
17 1     1   5 use constant NUMBER => '#';
  1         2  
  1         48  
18 1     1   4 use constant IPADDR => '.';
  1         2  
  1         43  
19 1     1   5 use constant ENDoFILE => '';
  1         1  
  1         9062  
20              
21             sub choke {
22 0     0 0   shift;
23 0           confess "parse error: ", @_
24             }
25              
26             sub set_toke($$;$) {
27 0     0 0   my($self, $token, $data) = @_;
28 0           $self->{_token} = $token;
29 0           $self->{_data} = $data;
30             }
31              
32              
33             sub where($;$) {
34 0     0 0   my $self = shift;
35 0 0         if (@_) {
36 0           $self->{_file} . ":" . $_[0]
37             } else {
38 0           $self->{_file} . ":" . $self->{_line}
39             }
40             }
41              
42             sub read_line($) {
43 0     0 0   my $self = shift;
44 0           $self->{_line}++;
45 0           chomp($self->{_curline} = $self->{_fh}->getline);
46             }
47              
48             sub check_comment($) {
49 0     0 0   my $self = shift;
50 0           for my $i ($self->{_curline}) {
51 0 0         $i=~m:\G#.*:gc and last;
52 0 0         $i=~m:\G//.*:gc and last;
53 0 0         if ($i=~m:\G/\*:gc) {
54 0           my($line) = $self->{_line};
55 0           until ($i=~m:\G.*?\*/:gc) {
56 0 0 0       $self->read_line || $i ne "" ||
57             $self->choke("EOF in comment starting at ",
58             $self->where($line));
59             }
60             }
61 0           return 0
62             }
63 0           return 1
64             }
65              
66             sub lex_string($) {
67 0     0 0   my $self = shift;
68 0           my($s, $line);
69 0           $line = $self->{_line};
70 0           $s = "";
71 0           LOOP: for my $i ($self->{_curline}) {
72             # the lexer in bind doesn't implement backslash escapes of any kind
73             # $i=~/\G([^"\\]+)/gc and do { $s .= $1; redo LOOP };
74             # $i=~/\G\\(["\\])/gc and do { $s .= $1; redo LOOP };
75 0 0         $i=~/\G([^"]+)/gc and do { $s .= $1; redo LOOP };
  0            
  0            
76 0 0         $i=~/\G"/gc and $self->set_toke(STRING, $s), return;
77             # Must be at the end of the line
78 0 0         if ($self->read_line) {
    0          
79 0           $s .= "\n";
80             } elsif ($i eq "") {
81 0           $self->choke("EOF in quoted string starting at ",
82             $self->where($line));
83             }
84 0           redo LOOP;
85             }
86             }
87              
88             sub lex_ident($$) {
89 0     0 0   my $self = shift;
90 0           my($i) = @_;
91 0   0       while (! $self->check_comment &&
92             $self->{_curline} =~ m:\G([^/"*!{};\s]+):gc) {
93 0           $i .= $1;
94             }
95 0           $self->set_toke(WORD, $i);
96             }
97              
98             sub lex_ipv4($$) {
99 0     0 0   my $self = shift;
100 0           my($i) = @_;
101 0           LOOP: for my $j ($self->{_curline}) {
102 0 0         $self->check_comment and last LOOP;
103 0 0         $j=~/\G(\d+)/gc and do { $i .= $1; redo LOOP };
  0            
  0            
104 0 0 0       $j=~/\G(\.\.)/gc ||
105             $j=~m:\G([^./"*!{};\s]+):gc and $self->lex_ident("$i$1"), return;
106 0 0         $j=~/\G\./gc and do { $i .= "."; redo LOOP };
  0            
  0            
107             }
108 0           my($dots);
109 0           $dots = $i =~ tr/././;
110 0 0 0       if ($dots > 3 || substr($i, -1) eq '.') {
111 0           $self->set_toke(WORD, $i);
112             return
113 0           }
114 0 0         if ($dots == 1) {
    0          
115 0           $i .= ".0.0";
116             } elsif ($dots == 2) {
117 0           $i .= ".0";
118             }
119 0           $self->set_toke(IPADDR, $i);
120             }
121              
122             sub lex_number($$) {
123 0     0 0   my $self = shift;
124 0           my($n) = @_;
125 0           LOOP: for my $i ($self->{_curline}) {
126 0 0         $self->check_comment and last LOOP;
127 0 0         $i=~/\G(\d+)/gc and do { $n .= $1; redo LOOP };
  0            
  0            
128 0 0         $i=~/\G\./gc and $self->lex_ipv4("$n."), return;
129 0 0         $i=~m:\G([^/"*!{};\s]+):gc and $self->lex_ident("$n$1"), return;
130             }
131 0           $self->set_toke(NUMBER, $n);
132             }
133              
134             sub lex($) {
135 0     0 0   my $self = shift;
136 0           OUTER: while(1) { for my $i ($self->{_curline}) {
  0            
137 0 0         INNER: {
138 0           $self->check_comment and last INNER;
139 0 0         $i=~/\G\s+/gc and redo;
140 0 0         $i=~m:\G([*/!{};]):gc and $self->set_toke($1), last OUTER;
141 0 0         $i=~/\G"/gc and $self->lex_string(), last OUTER;
142 0 0         $i=~/\G(\d+)/gc and $self->lex_number($1), last OUTER;
143 0 0         $i=~/\G(.)/gc and $self->lex_ident($1), last OUTER;
144             }
145 0 0         $i=~/\G\Z/gc or $self->choke("Unknown character at ", $self->where);
146 0 0 0       $self->read_line || $i ne "" or $self->set_toke(ENDoFILE), last OUTER;
147             } }
148 0           return $self;
149             }
150              
151             sub t2d($) {
152 0     0 0   my $self = shift;
153 0 0         $self->{_token} eq WORD and return qq('$self->{_data}');
154 0 0         $self->{_token} eq STRING and return qq("$self->{_data}");
155 0 0 0       $self->{_token} eq NUMBER ||
156             $self->{_token} eq IPADDR and return $self->{_data};
157 0 0         $self->{_token} eq ENDoFILE and return "";
158 0           return qq('$self->{_token}');
159             }
160              
161             sub t2n($;$) {
162 0     0 0   my($token, $need_article);
163 0           my($map) = {
164             WORD , [ an => "identifier"],
165             STRING , [ a => "string"],
166             NUMBER , [ a => "number"],
167             IPADDR , [ an => "IP address"],
168             ENDoFILE , [ "End of File"],
169             '*' , [ an => "asterisk"],
170             '!' , [ an => "exclamation point"],
171             '{' , [ an => "open brace"],
172             '}' , [ a => "close brace"],
173             ';' , [ a => "semicolon"],
174             }->{$token};
175 0 0         return "Fwuh? `$token'" unless $map;
176 0 0         if ($need_article) {
177 0           join(" ", @{ $map })
  0            
178             } else {
179 0           $map->[-1]
180             }
181             }
182              
183             sub expect($$$;$) {
184 0     0 0   my $self = shift;
185 0           my($token, $mess, $nolex) = @_;
186 0 0         $self->lex unless $nolex;
187 0 0         $token = [ $token ] unless ref $token;
188 0           foreach (@{ $token }) {
  0            
189 0 0         if (ref $_) {
190 0 0         next unless $self->{_token} eq WORD;
191 0           foreach (@$_) {
192 0 0         return if $_ eq $self->{_data};
193             }
194 0           $self->choke("Invalid identifier `", $self->{_data}, "' at ",
195             $self->where);
196             } else {
197 0 0         return if $_ eq $self->{_token};
198             }
199             }
200 0 0         if (@{ $token } == 1) {
  0            
201 0           $token = ${ $token }[0];
  0            
202 0 0         $token = WORD if ref $token;
203 0           $self->choke("Expected ", t2n($token, 1), ", saw ",
204             $self->t2d, " $mess at ", $self->where);
205             } else {
206 0           $self->choke("Unexpected ", t2n($self->{_token}), " (",
207             $self->t2d, ") $mess at ", $self->where);
208             }
209             }
210              
211             sub open_file($$) {
212 0     0 0   require IO::File;
213 0           my $self = shift;
214 0           my($file) = @_;
215 0 0         $self->{_fh} = IO::File->new($file, "r")
216             or croak "Unable to open $file for reading: $!";
217 0           $self->{_file} = $file;
218             }
219              
220             sub parse_bool($$) {
221 0     0 0   my($self, $mess) = @_;
222 0           $self->expect([ WORD, STRING, NUMBER ], $mess);
223 0           my($value) = {
224             "yes" => 1,
225             "no" => 0,
226             "true" => 1,
227             "false" => 0,
228             "1" => 1,
229             "0" => 0,
230             }->{$self->{_data}};
231 0 0         return $value if defined $value;
232 0           $self->choke("Expected a boolean, saw `", $self->{_data}, "' at ",
233             $self->where);
234             }
235             sub parse_addrmatchlist($$;$) {
236 0     0 0   my($self, $mess, $nolex) = @_;
237 0           $self->expect('{', $mess, $nolex);
238 0           my(@items, $negated, $data);
239 0           while(1) {
240 0           $negated = 0;
241 0           $self->expect([ IPADDR, NUMBER, WORD, STRING, '!', '{', '}' ], $mess);
242 0 0         last if $self->{_token} eq '}';
243 0 0         if ($self->{_token} eq '!') {
244 0           $negated = 1;
245 0           $self->expect([ IPADDR, NUMBER, WORD, STRING, '{' ],
246             "following `!'");
247             }
248 0 0         if ($self->{_token} eq '{') {
249 0           push @items, [ $negated, $self->parse_addrmatchlist($mess, 1) ];
250             next
251 0           }
252 0 0 0       if ($self->{_token} eq WORD || $self->{_token} eq STRING) {
253 0           push @items, [ $negated, "acl", $self->{_data} ];
254             next
255 0           }
256 0           $data = $self->{_data};
257 0 0         $self->expect( $self->{_token} eq NUMBER ? '/' : [ '/', ';' ], $mess);
258 0 0         if ($self->{_token} eq ';') {
259 0           push @items, [ $negated, $data ];
260             redo # we already slurped the ';'
261 0           }
262 0           $self->expect(NUMBER, "following `/'");
263 0           push @items, [ $negated, $data, $self->{_data} ];
264             } continue {
265 0           $self->expect(';', $mess);
266             }
267             return \@items
268 0           }
269             sub parse_addrlist($$) {
270 0     0 0   my($self, $mess) = @_;
271 0           $self->expect('{', $mess);
272 0           my(@addrs);
273 0           while (1) {
274 0           $self->expect([ IPADDR, '}' ], $mess);
275 0 0         last if $self->{_token} eq '}';
276 0           push @addrs, $self->{_data};
277 0           $self->expect(';', "reading address list");
278             }
279 0           return \@addrs;
280             # return \@addrs if @addrs;
281             # $self->choke("Expected at least one IP address, saw none at ",
282             # $self->where);
283             }
284             sub parse_size($$) {
285 0     0 0   my($self, $mess) = @_;
286 0           $self->expect([ WORD, STRING ], $mess);
287 0           my($data) = $self->{_data};
288 0 0         if ($data =~ /^(\d+)([kmg])$/i) {
289 0           return $1 * {
290             'k' => 1024,
291             'm' => 1024*1024,
292             'g' => 1024*1024*1024,
293             }->{lc($2)};
294             }
295 0           $self->choke("Expected size string, saw `$data' at ", $self->where);
296             }
297             sub parse_forward($$) {
298 0     0 0   my($self, $mess) = @_;
299 0           $self->expect([[qw(only first)]], $mess);
300 0           return $self->{_data};
301             }
302             sub parse_transfer_format($$) {
303 0     0 0   my($self, $mess) = @_;
304 0           $self->expect([[qw(one-answer many-answers)]], $mess);
305 0           return $self->{_data};
306             }
307             sub parse_check_names($$) {
308 0     0 0   my($self, $mess) = @_;
309 0           $self->expect([[qw(warn fail ignore)]], $mess);
310 0           return $self->{_data};
311             }
312             sub parse_pubkey($$) {
313 0     0 0   my($self, $mess) = @_;
314 0           my($flags, $proto, $algo);
315 0           $self->expect([ NUMBER, WORD, STRING ], $mess);
316 0           $flags = $self->{_data};
317 0 0         if ($self->{_token} ne NUMBER) {
318 0 0         $flags = oct($flags) if $flags =~ /^0/;
319             }
320 0           $self->expect(NUMBER, $mess);
321 0           $proto = $self->{_data};
322 0           $self->expect(NUMBER, $mess);
323 0           $algo = $self->{_data};
324 0           $self->expect(STRING, $mess);
325 0           return [ $flags, $proto, $algo, $self->{_data} ];
326             }
327              
328             sub parse_logging_category($) {
329 0     0 0   my $self = shift;
330 0           $self->expect([ WORD, STRING ], "following `category'");
331 0           my($name) = $self->{_data};
332 0           $self->expect('{', "following `category $name'");
333 0           my(@names);
334 0           while (1) {
335 0           $self->expect([ WORD, STRING, '}' ], "reading category `$name'");
336 0 0         last if $self->{_token} eq '}';
337 0           push @names, $self->{_data};
338 0           $self->expect(';', "reading category `$name'");
339             }
340 0           $self->expect(';', "to finish category `$name'");
341 0           $self->handle_logging_category($name, \@names);
342             }
343              
344             sub parse_logging_channel($) {
345 0     0 0   my $self = shift;
346 0           $self->expect([ WORD, STRING ], "following `channel'");
347 0           my($name) = $self->{_data};
348 0           $self->expect('{', "following `channel $name'");
349 0           my(%options, $temp);
350 0           while (1) {
351 0           $self->expect([ [ qw(file syslog null severity print-category
352             print-severity print-time) ], '}' ],
353             "reading channel `$name'");
354 0 0         last if $self->{_token} eq '}';
355 0           $temp = $self->{_data};
356 0 0         if ($temp =~ /^print-/) {
    0          
    0          
    0          
    0          
357 0           $options{$temp} = $self->parse_bool("following `$temp'");
358             } elsif ($temp eq "null") {
359 0 0         $self->choke("Destination already specified for channel `$name'")
360             if exists $options{dest};
361 0           $options{dest} = "null";
362             } elsif ($temp eq "file") {
363 0 0         $self->choke("Destination already specified for channel `$name'")
364             if exists $options{dest};
365 0           $self->expect(STRING, "following `file'");
366 0           $options{dest} = $self->{_data};
367 0           while(1) {
368 0           $self->expect([ [ qw(version size) ], ';' ],
369             "reading channel `$name' file specifier");
370 0 0         last if $self->{_token} eq ';';
371 0 0         if ($self->{_data} eq "size") {
372 0           $options{size} = $self->parse_size("following `size'");
373             } else { # versions
374 0           $self->expect([ WORD, NUMBER ], "following `versions'");
375 0 0         if ($self->{_token} eq NUMBER) {
    0          
376 0           $options{versions} = $self->{_data};
377             } elsif ($self->{_data} eq "unlimited") {
378 0           $options{versions} = -1;
379 0           } else { $self->choke("Unexpected identifier following ",
380             "`versions' at ", $self->where);
381             }
382             }
383             }
384             redo # already slurped ';'
385 0           } elsif ($temp eq "syslog") {
386 0 0         $self->choke("Destination already specified for channel `$name'")
387             if exists $options{dest};
388 0           $self->expect([[qw(kern user mail daemon auth syslog lpr news
389             uucp cron authpriv ftp local0 local1 local2
390             local3 local4 local5 local6 local7)]],
391             "following `syslog'");
392              
393 0           $options{dest} = "syslog " . $self->{_data};
394             } elsif ($temp eq "severity") {
395 0           $self->expect([[qw(critical error warning notice info debug
396             dynamic)]], "following `severity'");
397 0           $options{severity} = $self->{_data};
398 0 0         if ($options{severity} eq "debug") {
399 0           $self->expect([ NUMBER, ';' ], "reading channel `$name'");
400 0 0         if ($self->{_token} eq NUMBER) {
401 0           $options{severity} .= " " . $self->{_data};
402             } else {
403             redo # already slurped the ';'
404 0           }
405             }
406             }
407             } continue {
408 0           $self->expect(';', "reading channel `$name'");
409             }
410 0           $self->expect(';', "to finish channel `$name'");
411 0           $self->handle_logging_channel($name, \%options);
412             }
413              
414             sub parse_logging($) {
415 0     0 0   my $self = shift;
416 0           $self->expect('{', "following `logging'");
417 0           while (1) {
418 0           $self->expect([ [ qw(category channel) ], '}' ],
419             "reading logging options");
420 0 0         last if $self->{_token} eq '}';
421 0 0         if ($self->{_data} eq "category") {
422 0           $self->parse_logging_category;
423             } else { # channel
424 0           $self->parse_logging_channel;
425             }
426             }
427 0           $self->expect(';', "to finish logging declaration");
428             }
429              
430             my(%opt_table) = (
431             "version" => STRING,
432             "directory" => STRING,
433             "named-xfer" => STRING,
434             "dump-file" => STRING,
435             "memstatistics-file" => STRING,
436             "pid-file" => STRING,
437             "statistics-file" => STRING,
438             "auth-nxdomain" => \&parse_bool,
439             "deallocate-on-exit" => \&parse_bool,
440             "dialup" => \&parse_bool,
441             "fake-iquery" => \&parse_bool,
442             "fetch-glue" => \&parse_bool,
443             "has-old-clients" => sub {
444             my($self, $mess) = @_;
445             my($arg) = $self->parse_bool("following `has-old-clients'");
446             $self->handle_option("auth-nxdomain", $arg);
447             $self->handle_option("maintain-ixfr-base", $arg);
448             $self->handle_option("rfc2308-type1", ! $arg);
449             return (0, 0, 1);
450             },
451             "host-statistics" => \&parse_bool,
452             "multiple-cnames" => \&parse_bool,
453             "notify" => \&parse_bool,
454             "recursion" => \&parse_bool,
455             "rfc2308-type1" => \&parse_bool,
456             "use-id-pool" => \&parse_bool,
457             "treat-cr-as-space" => \&parse_bool,
458             "also-notify" => \&parse_addrlist,
459             "forward" => \&parse_forward,
460             "forwarders" => \&parse_addrlist,
461             "check-names" => sub {
462             my($self, $mess) = @_;
463             $self->expect([[qw(master slave response)]], $mess);
464             my($type);
465             $type = $self->{_data};
466             return [$type, $self->parse_check_names($mess)
467             ];
468             },
469             "allow-query" => \&parse_addrmatchlist,
470             "allow-transfer" => \&parse_addrmatchlist,
471             "allow-recursion" => \&parse_addrmatchlist,
472             "blackhole" => \&parse_addrmatchlist,
473             "listen-on" => sub {
474             my($self, $mess) = @_;
475             $self->expect([ [ 'port' ], '{' ], $mess);
476             my($port);
477             if ($self->{_token} eq WORD) {
478             $self->expect(NUMBER, "following `port'");
479             $port = 0 + $self->{_data};
480             $self->expect('{', $mess);
481             } else {
482             $port = 53;
483             }
484             return [$port, $self->parse_addrmatchlist($mess, 1)];
485             },
486             "query-source" => sub {
487             my($self, $mess) = @_;
488             my($port, $address) = (0, 0);
489             $self->expect([[qw(port address)]], $mess);
490             if ($self->{_data} eq "address") {
491             $self->expect([ IPADDR, '*' ], "following `address'");
492             $address = $self->{_token} eq '*' ? 0 : $self->{_data};
493             $self->expect([ [ 'port' ], ';' ], $mess);
494             if ($self->{_token} eq WORD) {
495             $self->expect([ NUMBER, '*' ], "following `port'");
496             $port = $self->{_token} eq '*' ? 0 : $self->{_data};
497             }
498             } else { #port
499             $self->expect([ NUMBER, '*' ], "following `port'");
500             $port = $self->{_token} eq '*' ? 0 : $self->{_data};
501             $self->expect([ [ 'address' ], ';' ], $mess);
502             if ($self->{_token} eq WORD) {
503             $self->expect([ IPADDR, '*' ], "following `address'");
504             $address = $self->{_token} eq '*' ? 0 : $self->{_data};
505             }
506             }
507             # Blech. We need to signal that we ate the ';'.
508             return ([$port, $address], $self->{_token} eq ';');
509             },
510             "lame-ttl" => NUMBER,
511             "max-transfer-time-in" => NUMBER,
512             "max-ncache-ttl" => NUMBER,
513             "min-roots" => NUMBER,
514             "serial-queries" => NUMBER,
515             "transfer-format" => \&parse_transfer_format,
516             "transfers-in" => NUMBER,
517             "transfers-out" => NUMBER,
518             "transfers-per-ns" => NUMBER,
519             "transfer-source" => IPADDR,
520             "maintain-ixfr-base" => \&parse_bool,
521             "max-ixfr-log-size" => NUMBER,
522             "coresize" => \&parse_size,
523             "datasize" => \&parse_size,
524             "files" => \&parse_size,
525             "stacksize" => \&parse_size,
526             "cleaning-interval" => NUMBER,
527             "heartbeat-interval" => NUMBER,
528             "interface-interval" => NUMBER,
529             "statistics-interval" => NUMBER,
530             "topology" => \&parse_addrmatchlist,
531             "sortlist" => \&parse_addrmatchlist,
532             "rrset-order" => sub {
533             my($self, $mess) = @_;
534             $self->expect('{', $mess);
535             my(@items, $class, $type, $name);
536             $mess = "while reading `rrset-order' list";
537             while(1) {
538             $class = $type = "any";
539             $name = "*";
540             $self->expect([[qw(class type name order)], '}'], $mess);
541             last if $self->{_token} eq '}';
542             if ($self->{_data} eq "class") {
543             $self->expect([ WORD, STRING ], "following `class'");
544             $class = lc($self->{_data});
545             $self->expect([[qw(type name order)]], $mess);
546             }
547             if ($self->{_data} eq "type") {
548             $self->expect([ WORD, STRING ], "following `type'");
549             $type = lc($self->{_data});
550             $self->expect([[qw(name order)]], $mess);
551             }
552             if ($self->{_data} eq "name") {
553             $self->expect(STRING, "following `name'");
554             $name = lc($self->{_data});
555             $self->expect([[qw(order)]], $mess);
556             }
557             # Must be 'order'
558             $self->expect(WORD, "following `order'");
559             push(@items, [$class, $type, $name, $self->{_data}]);
560             $self->expect(';', $mess);
561             }
562             return \@items;
563             },
564             );
565              
566             sub parse_key($) {
567 0     0 0   my $self = shift;
568 0           $self->expect([ WORD, STRING ], "following `key'");
569 0           my($key, $algo, $secret);
570 0           $key = $self->{_data};
571 0           $self->expect('{', "following key name `$key'");
572 0           $self->expect([[qw(algorithm secret)]], "reading key $key");
573 0 0         if ($self->{_data} eq "secret") {
574 0           $self->expect([ WORD, STRING ], "reading secret for key `$key'");
575 0           $secret = $self->{_data};
576 0           $self->expect(';', "reading key `$key'");
577 0           $self->expect([["algorithm"]], "reading key `$key'");
578 0           $self->expect([ WORD, STRING ], "reading algorithm for key `$key'");
579 0           $algo = $self->{_data};
580             } else {
581 0           $self->expect([ WORD, STRING ], "reading algorithm for key `$key'");
582 0           $algo = $self->{_data};
583 0           $self->expect(';', "reading key `$key'");
584 0           $self->expect([["secret"]], "reading key `$key'");
585 0           $self->expect([ WORD, STRING ], "reading secret for key `$key'");
586 0           $secret = $self->{_data};
587             }
588 0           $self->expect(';', "reading key `$key'");
589 0           $self->expect('}', "reading key `$key'");
590 0           $self->expect(';', "to finish key `$key'");
591 0           $self->handle_key($key, $algo, $secret);
592             }
593              
594             sub parse_controls($) {
595 0     0 0   my $self = shift;
596 0           $self->expect('{', "following `controls'");
597 0           while(1) {
598 0           $self->expect([ [ qw(inet unix) ], ';' ], "reading `controls'");
599 0 0         last if $self->{_token} eq ';';
600 0 0         if ($self->{_data} eq "inet") {
601 0           my($addr, $port);
602 0           $self->expect([ IPADDR, '*' ], "following `inet'");
603 0 0         $addr = $self->{_token} eq '*' ? 0 : $self->{_data};
604 0           $self->expect([["port"]], "following inet address");
605 0           $self->expect(NUMBER, "following `port'");
606 0           $port = 0 + $self->{_data};
607 0           $self->expect([["allow"]], "following port number");
608 0           $self->handle_control("inet", [ $addr, $port,
609             $self->parse_addrmatchlist("following `allow'") ]);
610             } else { # unix
611 0           my($path, $perm, $owner);
612 0           $self->expect(STRING, "following `unix'");
613 0           $path = $self->{_data};
614 0           $self->expect([["perm"]], "following socket path");
615 0           $self->expect(NUMBER, "following `perm'");
616 0           $perm = $self->{_data};
617 0           $self->expect([["owner"]], "following permissions");
618 0           $self->expect(NUMBER, "following `owner'");
619 0           $owner = $self->{_data};
620 0           $self->expect([["group"]], "following owner name");
621 0           $self->expect(NUMBER, "following `group'");
622 0           $self->handle_control("unix",
623             [ $path, $perm, $owner, $self->{_data} ]);
624             }
625             }
626 0           $self->expect('}', "finishing `controls'");
627             }
628              
629             sub parse_server($) {
630 0     0 0   my $self = shift;
631 0           $self->expect(IPADDR, "following `server'");
632 0           my($addr, %options);
633 0           $addr = $self->{_data};
634 0           $self->expect('{', "following `server $addr'");
635 0           while (1) {
636 0           $self->expect([ [ qw(bogus support-ixfr transfers
637             transfer-format keys) ] , '}' ],
638             "reading server `$addr'");
639 0 0         last if $self->{_token} eq '}';
640 0 0         if ($self->{_data} eq "bogus") {
641 0           $options{bogus} = $self->parse_bool("following `bogus'");
642             next
643 0           }
644 0 0         if ($self->{_data} eq "support-ixfr") {
645 0           $options{"support-ixfr"} =
646             $self->parse_bool("following `support-ixfr'");
647             next
648 0           }
649 0 0         if ($self->{_data} eq "transfers") {
650 0           $self->expect(NUMBER, "following `transfers'");
651 0           $options{transfers} = $self->{_data};
652             next
653 0           }
654 0 0         if ($self->{_data} eq "transfer-format") {
655 0           $options{"transfer-format"} =
656             $self->parse_transfer_format("following `transfer-format'");
657             next
658 0           }
659             # keys
660 0           $self->expect('{', "following `keys'");
661 0           my(@keys);
662 0           while (1) {
663 0           $self->expect([ WORD, STRING, '}' ], "reading key ids");
664 0 0         last if $self->{_token} eq '}';
665 0           push @keys, $self->{_data};
666             }
667 0           $options{"keys"} = \@keys;
668             } continue {
669 0           $self->expect(';', "reading server `$addr'");
670             }
671 0           $self->expect(';', "to finish server `$addr'");
672 0           $self->handle_server($addr, \%options);
673             }
674              
675             sub parse_trusted_keys($) {
676 0     0 0   my $self = shift;
677 0           $self->expect('{', "following `trusted-keys'");
678 0           my($domain, $flags, $proto, $algo);
679 0           while(1) {
680 0           $self->expect([ WORD, '}' ], "while reading key for `trusted-keys'");
681 0 0         last if $self->{_token} eq '}';
682 0           $domain = $self->{_data};
683 0           $self->handle_trusted_key($domain,
684             $self->parse_pubkey("while reading key for `trusted-keys'"));
685             }
686 0           $self->expect(';', "to finish trusted-keys");
687             }
688              
689             sub parse_zone($) {
690 0     0 0   my $self = shift;
691 0           my($name, $class);
692 0           $self->expect([ WORD, STRING ], "following `zone'");
693 0           $name = $self->{_data};
694 0           $self->expect([ WORD, STRING, '{', ';' ], "following `zone $name'");
695 0 0         if ($self->{_token} eq ';') {
    0          
696 0           $self->handle_empty_zone($name, 'in');
697             return
698 0           } elsif ($self->{_token} eq '{') {
699 0           $class = 'in';
700             } else {
701 0           $class = lc($self->{_data});
702 0           $self->expect([ '{', ';' ], "following `zone $name $class'");
703 0 0         if ($self->{_token} eq ';') {
704 0           $self->handle_empty_zone($name, $class);
705             return
706 0           }
707             }
708 0           my(%options, $temp);
709 0           while (1) {
710 0           $self->expect([ [ qw(type file masters transfer-source check-names
711             allow-update allow-query allow-transfer
712             max-transfer-time-in dialup notify also-notify
713             ixfr-base pubkey forward fowarders) ],
714             STRING, '}' ], "reading zone `$name'");
715 0 0         last if $self->{_token} eq '}';
716 0           $temp = $self->{_data};
717 0 0         if ($temp eq "type") {
718 0           $self->expect([[qw(master slave stub forward hint)]],
719             "following `$temp'");
720 0           $options{$temp} = $self->{_data};
721             next
722 0           }
723 0 0 0       if ($temp eq "file" || $temp eq "ixfr-base") {
724 0           $self->expect([ WORD, STRING ], "following `$temp'");
725 0           $options{$temp} = $self->{_data};
726             next
727 0           }
728 0 0 0       if ($temp eq "masters" || $temp eq "also-notify" ||
      0        
729             $temp eq "forwarders") {
730 0           $options{$temp} = $self->parse_addrlist("following `$temp'");
731             next
732 0           }
733 0 0 0       if ($temp eq "dialup" || $temp eq "notify") {
734 0           $options{$temp} = $self->parse_bool("following `$temp'");
735             next
736 0           }
737 0 0         if ($temp eq "max-transfer-time-in") {
738 0           $self->expect(NUMBER, "following `$temp'");
739 0           $options{$temp} = $self->{_data};
740             next
741 0           }
742 0 0         if ($temp eq "check-names") {
743 0           $options{$temp} = $self->parse_check_names("following `$temp'");
744             next
745 0           }
746 0 0         if ($temp eq "forward") {
747 0           $options{$temp} = $self->parse_forward("following `$temp'");
748             next
749 0           }
750 0 0         if ($temp eq "pubkey") {
751 0           $options{$temp} = $self->parse_pubkey("following `$temp'");
752             next
753 0           }
754 0           $options{$temp} = $self->parse_addrmatchlist("following `$temp'");
755             } continue {
756 0           $self->expect(';', "reading zone `$name'");
757             }
758 0           $self->expect(';', "to finish zone `$name'");
759 0 0         if (! exists $options{type}) {
760 0           $self->handle_empty_zone($name, $class, \%options);
761             } else {
762 0           $self->handle_zone($name, $class, $options{type}, \%options);
763             }
764             }
765              
766             sub parse_options($) {
767 0     0 0   my $self = shift;
768 0           $self->expect('{', "following `options'");
769 0           my($type, $option, $arg, $ate_semi, $did_handle_option);
770 0           while (1) {
771 0           $self->expect([ WORD, '}' ], "reading options");
772 0 0         last if $self->{_token} eq '}';
773 0           $option = $self->{_data};
774 0           $type = $opt_table{$option};
775 0           $ate_semi = $did_handle_option = 0;
776 0 0         if (ref $type) {
777 0           ($arg, $ate_semi, $did_handle_option) =
778             &$type($self, "following `$option'");
779             } else {
780 0           $self->expect($type, "following `$option'");
781 0           $arg = $self->{_data};
782             }
783 0 0         $self->expect(';', "following argument for option `$option'")
784             unless $ate_semi;
785 0 0         $self->handle_option($option, $arg)
786             unless $did_handle_option;
787             }
788 0           $self->expect(';', "to finish options");
789             }
790              
791             sub parse_conf() {
792 0     0 0   my $self = shift;
793 0           $self->{_curline} = '';
794 0           $self->{_flags} = { };
795 0           while (1) {
796 0           $self->expect([ ENDoFILE, WORD ], "at beginning of statement");
797 0 0         if ($self->{_token} eq ENDoFILE) {
798 0 0 0       if ($self->{_fhs} && @{$self->{_fhs}}) {
  0            
799 0           my($pos);
800 0           (@$self{qw(_fh _file _curline)}, $pos) =
801 0           @{ pop @{$self->{_fhs}} };
  0            
802 0           pos($self->{_curline}) = $pos;
803 0           redo;
804             }
805 0           last;
806             }
807 0 0         if ($self->{_data} eq "acl") {
808 0           $self->expect([ WORD, STRING ], "following `acl'");
809 0           my($name, $amlist);
810 0           $name = $self->{_data};
811 0           $amlist = $self->parse_addrmatchlist("reading acl `$name'");
812 0           $self->expect(';', "to finish acl `$name'");
813 0           $self->handle_acl($name, $amlist);
814             next
815 0           }
816 0 0         if ($self->{_data} eq "include") {
817 0           $self->expect(STRING, "following `include'");
818 0           my($include) = $self->{_data};
819 0           $self->expect(';', "reading include statement");
820 0           push @{$self->{_fhs}},
  0            
821             [ @$self{qw(_fh _file _curline)}, pos($self->{_curline}) ];
822 0           $self->open_file($include);
823             next
824 0           }
825 0 0         if ($self->{_data} eq "key") {
826 0           $self->parse_key;
827             next
828 0           }
829 0 0         if ($self->{_data} eq "logging") {
830 0 0         if ($self->{_flags}{seen_logging}++) {
831 0           $self->choke("Cannot redefine logging (", $self->where, ")");
832             }
833 0           $self->parse_logging;
834             next
835 0           }
836 0 0         if ($self->{_data} eq "options") {
837 0 0         if ($self->{_flags}{seen_options}++) {
838 0           $self->choke("Cannot redefine options (", $self->where, ")");
839             }
840 0           $self->parse_options;
841             next
842 0           }
843 0 0         if ($self->{_data} eq "controls") {
844 0           $self->parse_controls;
845             next
846 0           }
847 0 0         if ($self->{_data} eq "server") {
848 0           $self->parse_server;
849             next
850 0           }
851 0 0         if ($self->{_data} eq "trusted-keys") {
852 0           $self->parse_trusted_keys;
853             next
854 0           }
855 0 0         if ($self->{_data} eq "zone") {
856 0           $self->parse_zone;
857             next
858 0           }
859 0           $self->choke("Unknown configuration entry `", $self->{_data}, "' at ",
860             $self->where);
861             }
862             $self
863 0           }
864              
865             # The external entry points
866             sub new {
867 0     0 1   my $class = shift;
868 0           my $self = { };
869 0           bless $self, $class;
870 0           $self
871             }
872              
873             sub parse_file {
874 0     0 1   my $self = shift;
875 0 0         $self = $self->new unless ref $self;
876 0           $self->open_file(@_);
877 0           $self->{_line} = 0;
878 0           $self->parse_conf;
879             }
880              
881             sub parse_fh {
882 0     0 1   my $self = shift;
883 0 0         $self = $self->new unless ref $self;
884 0           $self->{_fh} = shift;
885 0 0         $self->{_file} = @_ ? shift : "a file handle";
886 0           $self->{_line} = 0;
887 0           $self->parse_conf;
888             }
889              
890             sub parse {
891 0     0 1   require IO::Scalar;
892 0           my $self = shift;
893 0           my $scalar = shift;
894 0 0         $self = $self->new unless ref $self;
895 0           $self->{_fh} = IO::Scalar->new(\$scalar);
896 0 0         $self->{_file} = @_ ? shift : "a scalar";
897 0           $self->{_line} = 0;
898 0           $self->parse_conf;
899             }
900              
901             # The callbacks
902 0     0 1   sub handle_logging_category {}; # $name, \@names
903 0     0 1   sub handle_logging_channel {}; # $name, \%options
904 0     0 1   sub handle_key {}; # $name, $algo, $secret
905 0     0 1   sub handle_acl {}; # $name, $addrmatchlist
906 0     0 1   sub handle_option {}; # $option, $argument
907 0     0 1   sub handle_server {}; # $name, \%options
908 0     0 1   sub handle_trusted_key {}; # $domain, [ $flags, $proto, $algo, $keydata ]
909 0     0 1   sub handle_empty_zone {}; # $name, $class, \%options
910 0     0 1   sub handle_zone {}; # $name, $class, $type, \%options
911 0     0 1   sub handle_control {}; # $socket_type, \@type_specific_data
912              
913             1;
914              
915             __END__