source: trunk/bind-import@ 810

Last change on this file since 810 was 810, checked in by Kris Deugau, 3 years ago

/trunk

Third sampled iteration of bind-import

File size: 5.0 KB
Line 
1#!/usr/bin/perl
2# Import a BIND zone 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
24use lib '.';
25use DNSDB;
26
27my $dnsdb = new DNSDB;
28my $doimport = 0;
29
30#print Dumper(\%reverse_typemap);
31
32my $zname = shift @ARGV;
33my $rev = 'n';
34my $zid;
35
36my %amap;
37my %namemap;
38
39if ($zname =~ /\.arpa\.?$/ || $zname =~ m,^[\d./]+$,) {
40 $rev = 'y';
41 $zname = _zone2cidr($zname) if $zname =~ /\.arpa\.?$/;
42 $zid = $dnsdb->revID($zname,':ANY:');
43 if ($zid) {
44 $zname = new NetAddr::IP $zname;
45 $zname = DNSDB::_ZONE($zname, 'ZONE', 'r', '.').($zname->{isv6} ? '.ip6.arpa' : '.in-addr.arpa');
46 }
47} else {
48 $zid = $dnsdb->domainID($zname,':ANY:');
49}
50
51die "zone $zname not on file\n" if !$zid;
52
53# still no sane way to expose a human-friendly view tag on the command line.
54my $view = shift @ARGV;
55$view = '' if !$view;
56
57##fixme: retrieve defttl from SOA record
58my $zonettl = 900;
59my $defttl = $zonettl;
60my $recbase = $zname; # to append to unqualified names
61
62# need to spin up a full state machine-ish thing, because BIND zone files are all about context
63while (<>) {
64 chomp;
65 next if /^\s*$/;
66 next if /^\s*;/;
67 if (my ($macro,$mdetail) = (/^\s*\$(TTL|ORIGIN|INCLUDE)\s+(.+)/) ) {
68 # macro sort of thing; $TTL and $ORIGIN most common. $INCLUDE is a thing, expect it to be rare in live use tho
69 if ($macro eq 'TTL') {
70 if ($mdetail =~ /^\d+$/) {
71 $defttl = $mdetail;
72 } else {
73 warn "invalid \$TTL: $_\n";
74 }
75 } elsif ($macro eq 'ORIGIN') {
76##fixme: going to skip the stupid case of "$ORIGIN com." and the like that lie
77# between . and the root domain we were told we're importing; anyone using such
78# a mess outside the root servers is clearly insane
79# handled cases:
80# $ORIGIN .
81# $ORIGIN [zonedomain].
82# $ORIGIN [subdomain.zonedomain].
83 if ($mdetail eq '.' || $mdetail =~ /$zname\.$/ || $zname =~ /$mdetail\.$/) {
84 $recbase = $mdetail;
85 } else {
86 # if we continue, we either use an $ORIGIN that's out of zone, or ignore it and potentially publish incorrect records.
87 die "bad \$ORIGIN: $_\n";
88 }
89 }
90 next;
91 }
92 # skip stale records that have no value
93 next if /^ip-192-168-1(12|20)-\d+/;
94 next if /ip.add.re.\d+\s*$/;
95 my ($name) = /([\w_.-]+)\s/;
96 # append zone name to record name if missing AND not dot-terminated;
97 # this happens automagically for forward zones, but not reverse because Reasons. (fixme?)
98 # suck up and deal with the error if the dot-termiated name is out of zone; should be
99 # impossible with valid BIND zone file but...
100 $name .= ".$zname" if $name !~ /$zname$/ && $zname !~ /\.$/;
101$name = $zname if /^\s*IN/;
102 s/([\w_.-]+)\s+//;
103 my ($class) = /(IN|CS|CH|HS)\s/;
104 if ($class) {
105 if ($class ne 'IN') {
106 print "Non-Internet class records not supported, you weirdo\n";
107 next;
108 }
109 s/(IN|CS|CH|HS)\s+//;
110 } else {
111 $class = 'IN' if !$class;
112 }
113 my ($ttl) = /(\d+)?\s/;
114 if (defined $ttl) {
115 # TTL may be zero
116 s/(\d+)?\s+//;
117 } else {
118 # Fall back to zone default TTL
119 $ttl = $zonettl;
120 }
121 my ($type) = /([A-Z-]+)\s/;
122 if (!$reverse_typemap{$type}) {
123 print "Unknown type $type, skipping\n";
124 next;
125 }
126 my $itype = $reverse_typemap{$type};
127 s/([A-Z-]+)\s+//;
128 chomp;
129 my $rdata = $_;
130
131 # Quotes may arguably be syntactically required, but they're not actually part of the record data
132 if ($itype == 16) {
133 $rdata =~ s/^"//;
134 $rdata =~ s/"$//;
135 }
136
137if ($type eq 'A') {
138# if ($amap{$name}) {
139# print "urp: dupe name $name $rdata\n";
140# } else {
141 push @{$amap{$name}}, $rdata;
142# }
143 push @{$namemap{$rdata}}, $name;
144}
145
146no warnings qw(uninitialized);
147#print "parsed: '$name' '$class' '$ttl' '$type'->'$itype' '$rdata'\n";
148#print;
149#;imap IN 900 CNAME deepnet.cx.
150##fixme: not sure how to handle the case where someone leaves off the class.
151 if ($doimport) {
152 my ($code, $msg);
153 if ($rev eq 'n') {
154 ($code,$msg) = $dnsdb->addRec('n', $rev, $zid, \$name, \$itype, \$rdata, $ttl);
155 } else {
156 ($code,$msg) = $dnsdb->addRec('n', $rev, $zid, \$rdata, \$itype, \$name, $ttl);
157 }
158 print "$code: $msg\n";
159 }
160}
161
162
163#print Dumper \%amap;
164foreach my $n (keys %amap) {
165 foreach my $ip (@{$amap{$n}}) {
166#print "$ip $n\n";
167 push @{$namemap{$ip}}, $n unless grep $n, @{$namemap{$ip}};
168 }
169}
170
171#print Dumper \%namemap;
172foreach my $ip (sort keys %namemap) {
173 print "$ip ".join(' ', @{$namemap{$ip}})."\n";
174}
Note: See TracBrowser for help on using the repository browser.