source: trunk/tiny-import.pl@ 355

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

/trunk

Checkpoint filling in tiny-import.pl stubs; NS records complete

  • Property svn:executable set to *
  • Property svn:keywords set to Date Rev Author Id
File size: 11.6 KB
Line 
1#!/usr/bin/perl
2# dnsadmin shell-based import tool for tinydns flatfiles
3##
4# $Id: tiny-import.pl 355 2012-06-29 18:50:17Z 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 my ($zone,$ip,$ns,$ttl,$stamp,$loc) = split /:/, $rec, 6;
182 $zone =~ s/^\&//;
183 $zone =~ s/\.$//;
184 $ttl = 0 if !$ttl;
185 $stamp = '' if !$stamp;
186 $loc = '' if !$loc;
187 $loc = '' if $loc =~ /^:+$/;
188 if ($zone =~ /\.arpa$/) {
189 ($code,$msg) = DNSDB::_zone2cidr($zone);
190 print "revzone $msg from $zone\n";
191 my ($rparent) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >>= ?", undef, ("$msg"));
192##fixme, in concert with the CNAME check for same; automagically
193# create "delegate" record instead for subzone NSes: convert above to use = instead of >>=
194# ($rparent) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?", undef, ("$msg"))
195# if !$rparent;
196 if ($rparent) {
197 $recsth->execute(0, $rparent, $ns, 2, $msg, 0, 0, 0, $ttl);
198 } else {
199 push @deferred, $rec unless $nodefer;
200 }
201 } else {
202 my $fparent = DNSDB::_hostparent($dbh, $zone);
203 if ($fparent) {
204 $recsth->execute($fparent, 0, $zone, 2, $ns, 0, 0, 0, $ttl);
205 } else {
206 push @deferred, $rec unless $nodefer;
207 }
208 }
209
210 } elsif ($rec =~ /^\^/) {
211 $cnt{PTR}++;
212
213 } elsif ($rec =~ /^\+/) {
214 $cnt{A}++;
215
216 } elsif ($rec =~ /^Z/) {
217 $cnt{SOA}++;
218
219 my ($zone,$master,$contact,$serial,$refresh,$retry,$expire,$minttl,$ttl,$stamp,$loc) = split /:/, $rec, 11;
220 $zone =~ s/^Z//;
221 $zone =~ s/\.$//;
222 $master =~ s/\.$//;
223 $contact =~ s/\.$//;
224 $ttl = 0 if !$ttl;
225 $stamp = '' if !$stamp;
226 $loc = '' if !$loc;
227 $loc = '' if $loc =~ /^:+$/;
228 if ($zone =~ /\.arpa$/) {
229 ($code,$msg) = DNSDB::_zone2cidr($zone);
230 $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,1,1)", undef, ($msg));
231 my ($rdns) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
232 $recsth->execute(0, $rdns, "$contact:$master", 6, "$refresh:$retry:$expire:$minttl", 0, 0, 0, $ttl);
233 } else {
234 $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,1,1)", undef, ($zone));
235 my ($domid) = $dbh->selectrow_array("SELECT currval('domains_domain_id_seq')");
236 $recsth->execute($domid, 0, "$contact:$master", 6, "$refresh:$retry:$expire:$minttl", 0, 0, 0, $ttl);
237 }
238
239 } elsif ($rec =~ /^\@/) {
240 $cnt{MX}++;
241
242 } elsif ($rec =~ /^'/) {
243 $cnt{TXT}++;
244
245 my ($fqdn, $rdata, $ttl, $stamp, $loc) = split /:/, $rec, 5;
246 $fqdn =~ s/^'//;
247 _deoctal(\$rdata);
248 $ttl = 0 if !$ttl;
249 $stamp = '' if !$stamp;
250 $loc = '' if !$loc;
251 $loc = '' if $loc =~ /^:+$/;
252
253 my $domid = DNSDB::_hostparent($dbh, $fqdn);
254 if ($domid) {
255 $recsth->execute($domid, 0, $fqdn, 16, $rdata, 0, 0, 0, $ttl);
256 } else {
257 push @deferred, $rec unless $nodefer;
258 }
259
260 } elsif ($rec =~ /^\./) {
261 $cnt{NSASOA}++;
262
263 my ($fqdn, $ip, $ns, $ttl, $stamp, $loc) = split /:/, $rec, 6;
264 $fqdn =~ s/^\.//;
265 $fqdn =~ s/\.$//;
266 $ns =~ s/\.$//;
267 $ns = "$ns.ns.$fqdn" if $ns !~ /\./;
268 $ttl = 0 if !$ttl;
269 $stamp = '' if !$stamp;
270 $loc = '' if !$loc;
271 $loc = '' if $loc =~ /^:+$/;
272
273 if ($fqdn =~ /\.arpa$/) {
274 ($code,$msg) = DNSDB::_zone2cidr($fqdn);
275 my ($rdns) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet = ?", undef, ($msg));
276 if (!$rdns) {
277 $errstr = "adding revzone $msg";
278 $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,1,1)", undef, ($msg));
279 ($rdns) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
280# this would probably make a lot more sense to do hostmaster.$config{admindomain}
281 $recsth->execute(0, $rdns, "hostmaster.$fqdn:$ns", 6, "16384:2048:1048576:2560", 0, 0, 0, "2560");
282 }
283 $recsth->execute(0, $rdns, $ns, 2, "$msg", 0, 0, 0, $ttl);
284##fixme: (?) implement full conversion of tinydns . records?
285# -> problem: A record for NS must be added to the appropriate *forward* zone, not the reverse
286#$recsth->execute(0, $rdns, $ns, 1, $ip, 0, 0, 0, $ttl)
287# ... auto-A-record simply does not make sense in reverse zones. Functionally
288# I think it would work, sort of, but it's a nasty mess and anyone hosting reverse
289# zones has names for their nameservers already.
290# Even the auto-nameserver-fqdn comes out... ugly.
291
292 } else {
293 my ($domid) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE lower(domain) = lower(?)",
294 undef, ($fqdn));
295 if (!$domid) {
296 $errstr = "adding domain $fqdn";
297 $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,1,1)", undef, ($fqdn));
298 ($domid) = $dbh->selectrow_array("SELECT currval('domains_domain_id_seq')");
299 $recsth->execute($domid, 0, "hostmaster.$fqdn:$ns", 6, "16384:2048:1048576:2560", 0, 0, 0, "2560");
300 }
301 $recsth->execute($domid, 0, $fqdn, 2, $ns, 0, 0, 0, $ttl);
302 $recsth->execute($domid, 0, $ns, 1, $ip, 0, 0, 0, $ttl) if $ip;
303 }
304
305
306 } elsif ($rec =~ /^\%/) {
307 $cnt{VIEWS}++;
308
309 } elsif ($rec =~ /^:/) {
310 $cnt{NCUST}++;
311# Big section. Since tinydns can publish anything you can encode properly, but only provides official
312# recognition and handling for the core common types, this must deal with the leftovers.
313# :fqdn:type:rdata:ttl:time:loc
314
315 my (undef, $fqdn, $type, $rdata, $ttl, $stamp, $loc) = split /:/, $rec, 7;
316 $ttl = 0 if !$ttl;
317 $stamp = '' if !$stamp;
318 $loc = '' if !$loc;
319 $loc = '' if $loc =~ /^:+$/;
320
321 if ($type == 33) {
322 # SRV
323 my ($prio, $weight, $port, $target) = (0,0,0,0);
324
325 my @tmp = _byteparse(\$rdata, 2);
326 $prio = $tmp[0] * 256 + $tmp[1];
327 @tmp = _byteparse(\$rdata, 2);
328 $weight = $tmp[0] * 256 + $tmp[1];
329 @tmp = _byteparse(\$rdata, 2);
330 $port = $tmp[0] * 256 + $tmp[1];
331
332 $rdata =~ s/\\\d{3}/./g;
333 ($target) = ($rdata =~ /^\.(.+)\.$/);
334# hmm. the above *should* work, but What If(TM) we have ASCII-range bytes
335# representing the target's fqdn part length(s)? axfr-get doesn't seem to,
336# probably because dec. 33->63 includes most punctuation and all the numbers
337# while ($rdata =~ /(\\\d{3})/) {
338# my $cnt = $1;
339# $rdata =~ s/^$cnt//;
340# $cnt =~ s/^\\/0/;
341# $cnt = oct($cnt);
342# my ($seg) = ($rdata =~ /^(.{$cnt})/);
343# $target .=
344# }
345
346 my $domid = DNSDB::_hostparent($dbh, $fqdn);
347 if ($domid) {
348 $recsth->execute($domid, 0, $fqdn, 33, $target, $prio, $weight, $port, $ttl) if $domid;
349 } else {
350 push @deferred, $rec unless $nodefer;
351 }
352
353 } elsif ($type == 28) {
354 # AAAA
355 my @v6;
356
357 for (my $i=0; $i < 8; $i++) {
358 my @tmp = _byteparse(\$rdata, 2);
359 push @v6, sprintf("%0.4x", $tmp[0] * 256 + $tmp[1]);
360 }
361 my $val = NetAddr::IP->new(join(':', @v6));
362
363 my ($rdns) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?", undef, ("$val"));
364 if ($rdns) {
365 $recsth->execute(0, $rdns, $fqdn, 28, $val->addr, 0, 0, 0, $ttl);
366 } else {
367 push @deferred, $rec unless $nodefer;
368 }
369
370 } else {
371 # ... uhhh, dunno
372 }
373
374 } else {
375 $cnt{other}++;
376 print " $_\n";
377 }
378 }
379
380 close FLAT;
381}
Note: See TracBrowser for help on using the repository browser.