source: trunk/bind2hosts@ 802

Last change on this file since 802 was 802, checked in by Kris Deugau, 4 years ago

/trunk

Commit 4th archived iteration of bind2hosts in development

  • Property svn:executable set to *
File size: 7.7 KB
RevLine 
[799]1#!/usr/bin/perl
2# Convert a BIND zone file to a hosts file
3##
4# Copyright 2020 Kris Deugau <kdeugau@deepnet.cx>
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18##
19
20use strict;
21use warnings;
22use Data::Dumper;
23
24# push "the directory the script is in" into @INC
25use FindBin;
26use lib "$FindBin::RealBin/";
27
28use DNSDB;
29
30my $dnsdb = new DNSDB;
31my $doimport = 0;
32
33#print Dumper(\%reverse_typemap);
34
35my $zname = shift @ARGV;
36my $rev = 'n';
37my $zid;
38
39my %amap;
40my %namemap;
[801]41my %cmap;
[799]42
[801]43# this bit irrelevant for a hosts file
44#if ($zname =~ /\.arpa\.?$/ || $zname =~ m,^[\d./]+$,) {
45# $rev = 'y';
46# $zname = _zone2cidr($zname) if $zname =~ /\.arpa\.?$/;
47# $zid = $dnsdb->revID($zname,':ANY:');
48# if ($zid) {
49# $zname = new NetAddr::IP $zname;
50# $zname = DNSDB::_ZONE($zname, 'ZONE', 'r', '.').($zname->{isv6} ? '.ip6.arpa' : '.in-addr.arpa');
51# }
52#} else {
53# $zid = $dnsdb->domainID($zname,':ANY:');
54#}
55#
56#die "zone $zname not on file\n" if !$zid;
[799]57
58# still no sane way to expose a human-friendly view tag on the command line.
59my $view = shift @ARGV;
60$view = '' if !$view;
61
62##fixme: retrieve defttl from SOA record
[801]63#my $zonettl = 900;
64#my $defttl = $zonettl;
65# need an ultimate fallback for this one
66my $defttl = 900;
67my $origin = "$zname."; # to append to unqualified names
68my $curlabel;
69my $prevlabel;
[799]70
[801]71my $i = 0;
72
[799]73# need to spin up a full state machine-ish thing, because BIND zone files are all about context
[801]74while (my $rec = <>) {
75 chomp $rec;
76 next if $rec =~ /^\s*$/;
77 next if $rec =~ /^\s*;/;
78 next if $rec =~ /^\s*\)/; # SOA closing (possibly other records too?)
[800]79 # arguably should do some more targeted voodoo when parsing the SOA details
[801]80#print "$i: ($rec)\n";
81#last if ++$i > 5;
[800]82
[801]83 if (my ($macro,$mdetail) = ($rec =~ /^\s*\$(TTL|ORIGIN|INCLUDE)\s+(.+)/) ) {
[799]84 # macro sort of thing; $TTL and $ORIGIN most common. $INCLUDE is a thing, expect it to be rare in live use tho
85 if ($macro eq 'TTL') {
[800]86 # irrelevant for a hosts file
[799]87 } elsif ($macro eq 'ORIGIN') {
[801]88#print "origin ($mdetail)\n";
[800]89 # $ORIGIN supports cascading/nesting, by watching for fully-qualified names vs partial names.
90 if ($mdetail =~ /\.$/) {
91 $origin = $mdetail;
[799]92 } else {
[800]93 # append current origin to unqualified origin
94 $origin = "$mdetail.$origin";
[799]95 }
96 }
[800]97##fixme: should arguably handle $INCLUDE
98# probably NOT going to handle $GENERATE, since that type of record set is best handled in DNS
[799]99 next;
100 }
[800]101
[801]102 my $origrec = $rec;
[800]103
104##fixme: convert to optional skipfile?
105# skip stale records that have no value
106#next if /^ip-\d+-\d+-\d+/;
107#next if /^ip.pre.fix.\d+.static.colo/;
108
109 # leading whitespace indicates "same label as last record"
[801]110 if ($rec =~ /^\s/) {
[800]111 $curlabel = $prevlabel;
112 } else {
[801]113 ($curlabel) = ($rec =~ /^([\w\@_.-]+)\s/);
[800]114 }
115
116 # magic name!
117 $curlabel = "$zname." if $curlabel eq '@';
118
119 # append $ORIGIN if name is not fully qualified.
120 if ($curlabel !~ /\.$/) {
[801]121 $curlabel .= ".$origin";
[800]122 }
123
124 # check for zone scope. skip bad records.
125 if ($curlabel !~ /$zname.$/) {
126 warn "bad record $origrec, maybe bad \$ORIGIN?\n";
127 next;
128 }
129
[801]130 # trim the label, if any
131 $rec =~ s/^([\w\@_.-]*)\s+//;
132
133#print "r$i ($rec)\n\t$curlabel\n";
134
135 my $nc = 0;
136my $debugid = -1;
137 my %seenatoms;
138# we don't actually use these but we have to recognize them
139my $class = 'IN';
140# not preset as we need to detect whether it's present in the record
141my $ttl;
142#my $ttl = $defttl;
143my $type;
144 my $badrec;
145 my $curatom = 'class';
146##fixme: maybe wrap this in an eval() instead of the warn/badrec/last bits?
147 for (; $nc < 3; $nc++) {
[802]148 my ($atom) = ($rec =~ /^([\w\d.]+)\s/);
[801]149 # should be safe?
150 last if !$atom;
151#print "a$nc: l: $rec\n $atom\n" if $i == $debugid;
152 if ($atom =~ /^\d+$/) {
153#print "a$nc: d: atom [$atom]\n $rec\n" if $i == $debugid;
154 if (defined($ttl)) {
155 warn "bad record ($origrec)\n";
156 $badrec = 1;
157 last;
158 } else {
159 if ($curatom ne 'class' && $curatom ne 'ttl') {
160 warn "bad record ($origrec)\n";
161 $badrec = 1;
162 last;
163 }
164 $curatom = 'ttl';
165 $ttl = $atom;
166 }
[799]167 }
[801]168 elsif ($atom =~ /^IN|CS|CH|HS$/) {
169#print "a$nc: d2: atom [$atom]\n $rec\n" if $i == $debugid;
170 if ($atom =~ /CS|CH|HS/) {
171 warn "unsupported class $atom in record ($origrec)\n";
172 $badrec = 1;
173 last;
174 }
175 $curatom = 'class';
176 $class = $atom;
177 }
178 elsif ($atom =~ /^[A-Z]+/) {
179#print "a$nc: d3a: probable type [$atom]\n" if $i == $debugid;
180 if ($reverse_typemap{$atom}) {
181#print "a$nc: d3b: atom [$atom]\n $rec\n" if $i == $debugid;
182 $type = $atom;
183 } else {
184 warn "unknown type $atom in record ($origrec)\n";
185 $badrec = 1;
186 last;
187 }
188 }
189 $rec =~ s/^$atom\s*//;
190#print "a$nc: next: $rec\n" if $i == $debugid;
191 } # class/type/TTL loop
192
193#last if $i > 15;
194 next if $badrec;
195
196#print Dumper(\%reverse_typemap);
197$ttl = $defttl if !defined($ttl);
198
199#print "class $class, ttl $ttl, type $type\n";
200#last;
201
[799]202 my $itype = $reverse_typemap{$type};
[801]203# s/([A-Z-]+)\s+//;
204# chomp;
205 my $rdata = $rec;
[799]206
[801]207 $prevlabel = $curlabel;
[799]208
[801]209##fixme: squish this down for this script since SOA records are irrelevant
210 if ($type eq 'SOA') {
211 my ($ns, $adminmail) = ($rdata =~ /([\w.]+)\s+([\w.]+)\s+\(/);
212 die "Can't parse gibberish SOAish record: $_\n" if !$ns;
213 $rdata =~ s/([\w.]+)\s+([\w.]+)\s+\(\s*//;
[799]214
[801]215 # There are probably more efficient ways to do this but the SOA record
216 # format is essentially character based, not line-based.
217 # In theory the SOA serial etc may be spread over up to 5 lines, in any combination.
[800]218
[801]219 # Parse fields from $rdata if present
220 my @soabits;
221 my @soafirst = split /\s+/, $rdata;
222 while (my $f = shift @soafirst) {
223 last if $f !~ /^\d/;
224 push @soabits, $f;
225 }
[799]226
[801]227 # Read more lines if we don't have enough SOA fields filled
228 while (scalar(@soabits) < 5) {
229 my $tmp = <>;
230 $tmp =~ s/^\s*//;
231 my @tmpsoa = split /\s+/, $tmp;
232 while (my $f = shift @tmpsoa) {
233 last if $f !~ /^\d/;
234 push @soabits, $f;
235 }
236 if (scalar(@soabits) == 5) {
237 last;
238 }
239 }
240 } # SOA
[799]241
[801]242##fixme: trim dupes if possible
243 elsif ($type eq 'A') {
244# push @{$amap{$curlabel}}, $rdata;
245# push @{$namemap{$rdata}}, $curlabel;
246
247 # need the name->IP map so we can reverse-map the CNAMEs on output
248 $amap{$curlabel}{$rdata}++;
249 $namemap{$rdata}{$curlabel}++;
250
251#print "$origrec\n";
252 } # A record
253
254 elsif ($type eq 'CNAME') {
255# push @{$cmap{$rdata}}, $curlabel;
256##todo: expand $rdata with $origin if unqualified
[802]257
258 $cmap{$curlabel} = $rdata.($rdata =~ /\./ ? '' : ".$origin");
[801]259#print "$origrec\n";
260 } # CNAME record
261
262
263# last if ++$i > 10;
264} # <STDIN>
265
266
[799]267#print Dumper \%amap;
[801]268#foreach my $n (keys %amap) {
269# foreach my $ip (@{$amap{$n}}) {
270##print "$ip\t$n\n";
271# push @{$namemap{$ip}}, $n unless grep $n, @{$namemap{$ip}};
272# }
273#}
[799]274
275#print Dumper \%namemap;
[801]276#foreach my $ip (sort keys %namemap) {
277# print "$ip\t".join(' ', @{$namemap{$ip}})."\n";
278#}
279
280#print Dumper \%cmap;
281
282
283foreach my $cn (keys %cmap) {
284 print "$cn -> $cmap{$cn}\n";
285# warn "CNAME $cn out of zone\n" if !$namemap{$cn};
[799]286}
Note: See TracBrowser for help on using the repository browser.