2 use 5.010; |
2 use 5.010; |
3 use strict; |
3 use strict; |
4 use warnings; |
4 use warnings; |
5 use File::Temp; |
5 use File::Temp; |
6 use Smart::Comments; |
6 use Smart::Comments; |
|
7 use Digest::SHA qw(sha512_hex); |
7 |
8 |
8 sub parse { |
9 sub parse { |
9 my $file = shift; |
10 my $file = shift; |
10 my @lines = split /\n/, do { |
11 my @lines = split /\n/, do { |
11 local $/ = undef; |
12 local $/ = undef; |
12 local @ARGV = $file; |
13 local @ARGV = $file; |
13 <>; |
14 <>; |
14 }; |
15 }; |
15 |
16 |
16 my @zone; |
17 my @zone; |
|
18 my ($origin, $ttl, $last_label); |
17 |
19 |
18 foreach (@lines) { |
20 foreach (@lines) { |
|
21 s{;.*$}{}; |
19 given ($_) { |
22 given ($_) { |
20 when (m{^;}) { next } |
23 when (m{^\s*\$ORIGIN\s+(\S+)}) { $origin = $1 } |
|
24 when (m{^\s*\$TTL\s+(\S+)}) { $ttl = $1 } |
21 when ( |
25 when ( |
22 m{^(?<label>\S+) |
26 m{^(?<label>\S+)? |
23 \s+(?<ttl>\S+(?=\s+)) |
27 \s+(?<ttl>\S+(?=\s+)) |
24 \s+(?:(?:IN|ANY)\s+)?(?<rr>\S+(?=\s+)) |
28 \s+(?:(?:IN|ANY)\s+)?(?<rr>\S+(?=\s+)) |
25 \s+(?<data>.*) |
29 \s+(?<data>.*) |
26 }x |
30 }x |
27 ) |
31 ) |
28 { |
32 { |
29 push @zone, { |
33 my %rrset = ( |
30 label => $+{label}, |
34 label => $last_label = defined $+{label} |
31 ttl => $+{ttl}, |
35 ? $+{label} eq '@' ? $origin : $+{label} |
|
36 : $last_label, |
|
37 ttl => $+{ttl} // $ttl, |
32 rr => uc $+{rr}, |
38 rr => uc $+{rr}, |
33 data => $+{data}, |
39 data => $+{data}, |
34 }; |
40 ); |
|
41 |
|
42 # label ergänzen, wenn nicht FQDN |
|
43 $rrset{label} .= ".$origin" unless substr($rrset{label}, -1) eq '.'; |
|
44 |
|
45 given ($rrset{rr}) { |
|
46 # origin steht im SOA |
|
47 when('SOA') { $origin = $rrset{label} } |
|
48 # bei einigen RRs müssen wir die Daten korrigieren |
|
49 when ([qw/MX A NS PTR/]) { |
|
50 $rrset{data} .= ".$origin" unless substr($rrset{data}, -1) eq '.'; |
|
51 } |
|
52 } |
|
53 my $id = sha512_hex(sort %rrset); |
|
54 push @zone, {id => $id, rrset => \%rrset}; |
35 } |
55 } |
36 } |
56 } |
37 } |
57 } |
38 |
58 |
39 return @zone; |
59 return @zone; |
40 |
|
41 } |
60 } |
42 |
61 |
43 sub nice { |
62 sub nice { |
|
63 # get a list of { id => $id, rrset => \%rrset } |
44 my @zone = |
64 my @zone = |
45 sort { length $a->{label} <=> length $b->{label} or $a->{label} cmp $b->{label}} |
65 sort { length $a->{label} <=> length $b->{label} or $a->{label} |
46 grep { !($_->{rr} ~~ [qw(RRSIG NSEC3 NSEC3PARAM NSEC DNSKEY TSIG)]) } @_; |
66 cmp $b->{label}} map { $_->{rrset} } @_; |
47 |
67 |
|
68 my @out; |
48 my $origin = (grep { $_->{rr} eq 'SOA' } @zone)[0]->{label}; |
69 my $origin = (grep { $_->{rr} eq 'SOA' } @zone)[0]->{label}; |
49 my $ttl = (grep { $_->{rr} eq 'SOA' } @zone)[0]->{ttl}; |
70 my $ttl = (grep { $_->{rr} eq 'SOA' } @zone)[0]->{ttl}; |
50 my $l1 = (sort map { index $_->{label}, '.' } @zone)[-1]; |
71 my $l1 = (sort map { index $_->{label}, '.' } @zone)[-1]; |
51 my $l2 = (sort map { length $_->{rr} } @zone)[-1]; |
72 my $l2 = (sort map { length $_->{rr} } @zone)[-1]; |
52 print "\$ORIGIN $origin\n"; |
73 push @out, "\$ORIGIN $origin", |
53 print "\$TTL $ttl\n"; |
74 "\$TTL $ttl"; |
54 |
75 |
55 my $print = sub { |
76 my $print = sub { |
56 my %r = %{+shift}; |
77 my %r = %{+shift}; |
57 state $last_label;; |
78 state $last_label;; |
58 |
79 |
63 $r{label} = do { |
84 $r{label} = do { |
64 if (defined $last_label and $r{label} eq $last_label) { '' } |
85 if (defined $last_label and $r{label} eq $last_label) { '' } |
65 else { $last_label = $r{label} } |
86 else { $last_label = $r{label} } |
66 }; |
87 }; |
67 |
88 |
68 my $rc = sprintf "%-*s %6s %-*s %s\n", |
89 return sprintf '%-*s %6s %-*s %s', |
69 $l1 => $r{label}, $ttl, $l2 => $r{rr}, $r{data}; |
90 $l1 => $r{label}, $ttl, $l2 => $r{rr}, $r{data}; |
|
91 }; |
|
92 push @out, $print->($_) foreach @zone; |
|
93 return join "\n", @out; |
|
94 } |
70 |
95 |
71 return $rc; |
96 sub delta { |
72 }; |
97 my ($zone1, $zone2) = @_; |
73 foreach (@zone) { |
98 my %zone1 = map { $_->{id}, $_->{rrset} } @$zone1; |
74 print $print->($_); |
99 my %zone2 = map { $_->{id}, $_->{rrset} } @$zone2; |
75 } |
100 #delete @zone1{keys %zone2}; |
76 return 0; |
101 #delete @zone2{keys %zone1}; |
|
102 ### %zone1 |
|
103 ### %zone2 |
|
104 exit; |
77 } |
105 } |
78 |
106 |
79 sub main { |
107 sub main { |
80 my ($file) = @_; |
108 my ($file) = @_; |
81 my @zone = parse($file); |
109 my @zone1 = grep { |
82 print nice(@zone); |
110 # get { id => $id, rrset => \%rrset } |
|
111 #not $_->{rrset}{rr} ~~ [qw(RRSIG NSEC3 NSEC3PARAM NSEC DNSKEY TSIG)] |
|
112 $_->{rrset}{rr} ~~ [qw(SOA NS MX)] |
|
113 } parse($file); |
|
114 my $tmp = File::Temp->new(); |
|
115 $tmp->print(nice @zone1); |
|
116 $tmp->close(); |
|
117 system 'cat' => $tmp->filename; |
|
118 my @zone2 = parse($tmp->filename); |
|
119 delta(\@zone1, \@zone2); |
83 exit; |
120 exit; |
84 #foreach (map {$_->[1]} @zone) { |
|
85 # next if not %{$_}; |
|
86 # next if $_->{rr} ~~ [qw(DNSKEY RRSIG NSEC3 NSECPARAM NSEC)]; |
|
87 # say $_->{data}; |
|
88 # } |
|
89 } |
121 } |
90 |
122 |
91 exit main(@ARGV) if not caller; |
123 exit main(@ARGV) if not caller; |