source: trunk/tiny-import.pl@ 354

Last change on this file since 354 was 354, checked in by Kris Deugau, 12 years ago

/trunk

Checkpoint on tiny-import.pl

  • normalize some whitespace now that the code seems to be working
  • remove some stale working comments
  • Property svn:executable set to *
  • Property svn:keywords set to Date Rev Author Id
File size: 10.6 KB
Line 
1#!/usr/bin/perl
2# dnsadmin shell-based import tool for tinydns flatfiles
3##
4# $Id: tiny-import.pl 354 2012-06-29 16:49:20Z kdeugau $
5# Copyright 2012 Kris Deugau <kdeugau@deepnet.cx>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19##
20
21use strict;
22use warnings;
23
24use lib '.';
25use DNSDB qw(:ALL);
26
27if (!loadConfig()) {
28 warn "Using default configuration; unable to load custom settings: $DNSDB::errstr";
29}
30
31my $code;
32my ($dbh,$msg) = connectDB($config{dbname}, $config{dbuser}, $config{dbpass}, $config{dbhost});
33initGlobals($dbh) if $dbh;
34
35$dbh->{AutoCommit} = 0;
36$dbh->{RaiseError} = 1;
37
38my %cnt;
39my @deferred;
40my $errstr = '';
41
42foreach my $file (@ARGV) {
43 eval {
44 import(file => $file);
45# import(file => $file, nosoa => 1);
46 $dbh->rollback;
47# $dbh->commit;
48 };
49 if ($@) {
50 print "bleh: $@\n";
51die "die harder: $errstr\n";
52 }
53}
54
55 foreach (keys %cnt) {
56 print " $_ $cnt{$_}\n";
57 }
58
59exit 0;
60
61sub import {
62 our %args = @_;
63 my $flatfile = $args{file};
64 open FLAT, "<$flatfile";
65
66 our $recsth = $dbh->prepare("INSERT INTO records (domain_id,rdns_id,host,type,val,distance,weight,port,ttl) ".
67 " VALUES (?,?,?,?,?,?,?,?,?)");
68
69 my %deleg;
70
71 while (<FLAT>) {
72 next if /^#/;
73 next if /^\s*$/;
74 chomp;
75 recslurp($_);
76 }
77
78 # Try the deferred records again, once.
79 foreach (@deferred) {
80 # print "trying $_ again\n";
81 recslurp($_, 1);
82 }
83
84print scalar(@deferred)." deferred records in $flatfile\n";
85
86 # Sub for various nonstandard types with lots of pure bytes expressed in octal
87 # Takes a tinydns rdata string and count, returns a list of $count bytes as well
88 # as trimming those logical bytes off the front of the rdata string.
89 sub _byteparse {
90 my $src = shift;
91 my $count = shift;
92 my @ret;
93 for (my $i = 0; $i < $count; $i++) {
94 if ($$src =~ /^\\/) {
95 # we should have an octal bit
96 my ($tmp) = ($$src =~ /^(\\\d{3})/);
97 $tmp =~ s/\\/0/;
98 push @ret, oct($tmp);
99 $$src =~ s/^\\\d{3}//;
100 } else {
101 # we seem to have a byte expressed as an ASCII character
102 my ($tmp) = ($$src =~ /^(.)/);
103 push @ret, ord($tmp);
104 $$src =~ s/^.//;
105 }
106 }
107 return @ret;
108 }
109
110 # Convert octal-coded bytes back to something resembling normal characters, general case
111 sub _deoctal {
112 my $targ = shift;
113 while ($$targ =~ /\\(\d{3})/) {
114 my $sub = chr(oct($1));
115 $$targ =~ s/\\$1/$sub/g;
116 }
117 }
118
119 sub recslurp {
120 my $rec = shift;
121 my $nodefer = shift || 0;
122
123 if ($rec =~ /^=/) {
124 $cnt{APTR}++;
125
126##fixme: do checks like this for all types
127 if ($rec !~ /^=(?:\*|\\052)?[a-z0-9\._-]+:[\d\.]+:\d*/i) {
128 print "bad A+PTR $rec\n";
129 return;
130 }
131 my ($host,$ip,$ttl,$stamp,$loc) = split /:/, $rec, 5;
132 $host =~ s/^=//;
133 $host =~ s/\.$//;
134 $ttl = 0 if !$ttl;
135 $stamp = '' if !$stamp;
136 $loc = '' if !$loc;
137 $loc = '' if $loc =~ /^:+$/;
138 my $fparent = DNSDB::_hostparent($dbh, $host);
139 my ($rparent) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?", undef, ($ip));
140 if ($fparent && $rparent) {
141 $recsth->execute($fparent, $rparent, $host, 65280, $ip, 0, 0, 0, $ttl);
142 } else {
143 push @deferred, $rec unless $nodefer;
144 # print "$tmporig deferred; can't find both forward and reverse zone parents\n";
145 }
146
147 } elsif ($rec =~ /^C/) {
148 $cnt{CNAME}++;
149
150 my ($host,$targ,$ttl,$stamp,$loc) = split /:/, $rec, 5;
151 $host =~ s/^C//;
152 $host =~ s/\.$//;
153 $ttl = 0 if !$ttl;
154 $stamp = '' if !$stamp;
155 $loc = '' if !$loc;
156 $loc = '' if $loc =~ /^:+$/;
157 if ($host =~ /\.arpa$/) {
158 ($code,$msg) = DNSDB::_zone2cidr($host);
159 my ($rparent) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?", undef, ($msg));
160 $recsth->execute(0, $rparent, $targ, 5, $msg->addr, 0, 0, 0, $ttl);
161
162##fixme: automagically convert manually maintained sub-/24 delegations
163# my ($subip, $zone) = split /\./, $targ, 2;
164# ($code, $msg) = DNSDB::_zone2cidr($zone);
165# push @{$deleg{"$msg"}{iplist}}, $subip;
166#print "$msg $subip\n";
167
168 } else {
169 my $fparent = DNSDB::_hostparent($dbh, $host);
170 if ($fparent) {
171 $recsth->execute($fparent, 0, $host, 5, $targ, 0, 0, 0, $ttl);
172 } else {
173 push @deferred, $rec unless $nodefer;
174 # print "$tmporig deferred; can't find parent zone\n";
175 }
176 }
177
178 } elsif ($rec =~ /^\&/) {
179 $cnt{NS}++;
180
181 } elsif ($rec =~ /^\^/) {
182 $cnt{PTR}++;
183
184 } elsif ($rec =~ /^\+/) {
185 $cnt{A}++;
186
187 } elsif ($rec =~ /^Z/) {
188 $cnt{SOA}++;
189
190 my ($zone,$master,$contact,$serial,$refresh,$retry,$expire,$minttl,$ttl,$stamp,$loc) = split /:/, $rec, 11;
191 $zone =~ s/^Z//;
192 $zone =~ s/\.$//;
193 $master =~ s/\.$//;
194 $contact =~ s/\.$//;
195 $ttl = 0 if !$ttl;
196 $stamp = '' if !$stamp;
197 $loc = '' if !$loc;
198 $loc = '' if $loc =~ /^:+$/;
199 if ($zone =~ /\.arpa$/) {
200 ($code,$msg) = DNSDB::_zone2cidr($zone);
201 $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,1,1)", undef, ($msg));
202 my ($rdns) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
203 $recsth->execute(0, $rdns, "$contact:$master", 6, "$refresh:$retry:$expire:$minttl", 0, 0, 0, $ttl);
204 } else {
205 $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,1,1)", undef, ($zone));
206 my ($domid) = $dbh->selectrow_array("SELECT currval('domains_domain_id_seq')");
207 $recsth->execute($domid, 0, "$contact:$master", 6, "$refresh:$retry:$expire:$minttl", 0, 0, 0, $ttl);
208 }
209
210 } elsif ($rec =~ /^\@/) {
211 $cnt{MX}++;
212
213 } elsif ($rec =~ /^'/) {
214 $cnt{TXT}++;
215
216 my ($fqdn, $rdata, $ttl, $stamp, $loc) = split /:/, $rec, 5;
217 $fqdn =~ s/^'//;
218 _deoctal(\$rdata);
219 $ttl = 0 if !$ttl;
220 $stamp = '' if !$stamp;
221 $loc = '' if !$loc;
222 $loc = '' if $loc =~ /^:+$/;
223
224 my $domid = DNSDB::_hostparent($dbh, $fqdn);
225 if ($domid) {
226 $recsth->execute($domid, 0, $fqdn, 16, $rdata, 0, 0, 0, $ttl);
227 } else {
228 push @deferred, $rec unless $nodefer;
229 }
230
231 } elsif ($rec =~ /^\./) {
232 $cnt{NSASOA}++;
233
234 my ($fqdn, $ip, $ns, $ttl, $stamp, $loc) = split /:/, $rec, 6;
235 $fqdn =~ s/^\.//;
236 $fqdn =~ s/\.$//;
237 $ns =~ s/\.$//;
238 $ns = "$ns.ns.$fqdn" if $ns !~ /\./;
239 $ttl = 0 if !$ttl;
240 $stamp = '' if !$stamp;
241 $loc = '' if !$loc;
242 $loc = '' if $loc =~ /^:+$/;
243
244 if ($fqdn =~ /\.arpa$/) {
245 ($code,$msg) = DNSDB::_zone2cidr($fqdn);
246 my ($rdns) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet = ?", undef, ($msg));
247 if (!$rdns) {
248 $errstr = "adding revzone $msg";
249 $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,1,1)", undef, ($msg));
250 ($rdns) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
251# this would probably make a lot more sense to do hostmaster.$config{admindomain}
252 $recsth->execute(0, $rdns, "hostmaster.$fqdn:$ns", 6, "16384:2048:1048576:2560", 0, 0, 0, "2560");
253 }
254 $recsth->execute(0, $rdns, $ns, 2, "$msg", 0, 0, 0, $ttl);
255##fixme: (?) implement full conversion of tinydns . records?
256# -> problem: A record for NS must be added to the appropriate *forward* zone, not the reverse
257#$recsth->execute(0, $rdns, $ns, 1, $ip, 0, 0, 0, $ttl)
258# ... auto-A-record simply does not make sense in reverse zones. Functionally
259# I think it would work, sort of, but it's a nasty mess and anyone hosting reverse
260# zones has names for their nameservers already.
261# Even the auto-nameserver-fqdn comes out... ugly.
262
263 } else {
264 my ($domid) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE lower(domain) = lower(?)",
265 undef, ($fqdn));
266 if (!$domid) {
267 $errstr = "adding domain $fqdn";
268 $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,1,1)", undef, ($fqdn));
269 ($domid) = $dbh->selectrow_array("SELECT currval('domains_domain_id_seq')");
270 $recsth->execute($domid, 0, "hostmaster.$fqdn:$ns", 6, "16384:2048:1048576:2560", 0, 0, 0, "2560");
271 }
272 $recsth->execute($domid, 0, $fqdn, 2, $ns, 0, 0, 0, $ttl);
273 $recsth->execute($domid, 0, $ns, 1, $ip, 0, 0, 0, $ttl) if $ip;
274 }
275
276
277 } elsif ($rec =~ /^\%/) {
278 $cnt{VIEWS}++;
279
280 } elsif ($rec =~ /^:/) {
281 $cnt{NCUST}++;
282# Big section. Since tinydns can publish anything you can encode properly, but only provides official
283# recognition and handling for the core common types, this must deal with the leftovers.
284# :fqdn:type:rdata:ttl:time:loc
285
286 my (undef, $fqdn, $type, $rdata, $ttl, $stamp, $loc) = split /:/, $rec, 7;
287 $ttl = 0 if !$ttl;
288 $stamp = '' if !$stamp;
289 $loc = '' if !$loc;
290 $loc = '' if $loc =~ /^:+$/;
291
292 if ($type == 33) {
293 # SRV
294 my ($prio, $weight, $port, $target) = (0,0,0,0);
295
296 my @tmp = _byteparse(\$rdata, 2);
297 $prio = $tmp[0] * 256 + $tmp[1];
298 @tmp = _byteparse(\$rdata, 2);
299 $weight = $tmp[0] * 256 + $tmp[1];
300 @tmp = _byteparse(\$rdata, 2);
301 $port = $tmp[0] * 256 + $tmp[1];
302
303 $rdata =~ s/\\\d{3}/./g;
304 ($target) = ($rdata =~ /^\.(.+)\.$/);
305# hmm. the above *should* work, but What If(TM) we have ASCII-range bytes
306# representing the target's fqdn part length(s)? axfr-get doesn't seem to,
307# probably because dec. 33->63 includes most punctuation and all the numbers
308# while ($rdata =~ /(\\\d{3})/) {
309# my $cnt = $1;
310# $rdata =~ s/^$cnt//;
311# $cnt =~ s/^\\/0/;
312# $cnt = oct($cnt);
313# my ($seg) = ($rdata =~ /^(.{$cnt})/);
314# $target .=
315# }
316
317 my $domid = DNSDB::_hostparent($dbh, $fqdn);
318 if ($domid) {
319 $recsth->execute($domid, 0, $fqdn, 33, $target, $prio, $weight, $port, $ttl) if $domid;
320 } else {
321 push @deferred, $rec unless $nodefer;
322 }
323
324 } elsif ($type == 28) {
325 # AAAA
326 my @v6;
327
328 for (my $i=0; $i < 8; $i++) {
329 my @tmp = _byteparse(\$rdata, 2);
330 push @v6, sprintf("%0.4x", $tmp[0] * 256 + $tmp[1]);
331 }
332 my $val = NetAddr::IP->new(join(':', @v6));
333
334 my ($rdns) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?", undef, ("$val"));
335 if ($rdns) {
336 $recsth->execute(0, $rdns, $fqdn, 28, $val->addr, 0, 0, 0, $ttl);
337 } else {
338 push @deferred, $rec unless $nodefer;
339 }
340
341 } else {
342 # ... uhhh, dunno
343 }
344
345 } else {
346 $cnt{other}++;
347 print " $_\n";
348 }
349 }
350
351 close FLAT;
352}
Note: See TracBrowser for help on using the repository browser.