source: trunk/tiny-import.pl@ 353

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

/trunk

Checkpoint filling in tinydns data import stubs

  • Move a useful sub up the hierarchy
  • Include explicit count/limit in split() calls; lets us catch possible syntax oopses that would give strange timestamp or location entries
  • Convert existing record inserts to use prepared statement
  • Extend CNAME support to handle CNAMEs properly in reverse zones
  • Mostly finish tinydns "." type (SOA+NS+A) - note these seem to be mainly useful for very small installations, not large ones.
  • Add stub for location/view entries

See #26, partly

  • Property svn:executable set to *
  • Property svn:keywords set to Date Rev Author Id
File size: 10.8 KB
Line 
1#!/usr/bin/perl
2# dnsadmin shell-based import tool for tinydns flatfiles
3##
4# $Id: tiny-import.pl 353 2012-06-27 21:20:54Z 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}++;
125if ($rec !~ /^=(?:\*|\\052)?[a-z0-9\._-]+:[\d\.]+:\d*/i) {
126 print "bad A+PTR $rec\n";
127 return;
128#=sud-rr-iGi0-1_sud-gw1-iGi4-2.vianet.ca::10.10.10.13:900::in
129}
130 my ($host,$ip,$ttl,$stamp,$loc) = split /:/, $rec, 5;
131 $host =~ s/^=//;
132 $host =~ s/\.$//;
133 $ttl = 0 if !$ttl;
134 $stamp = '' if !$stamp;
135 $loc = '' if !$loc;
136 $loc = '' if $loc =~ /^:+$/;
137 my $fparent = DNSDB::_hostparent($dbh, $host);
138 my ($rparent) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?", undef, ($ip));
139 if ($fparent && $rparent) {
140 $recsth->execute($fparent, $rparent, $host, 65280, $ip, 0, 0, 0, $ttl);
141 } else {
142 push @deferred, $rec unless $nodefer;
143 # print "$tmporig deferred; can't find both forward and reverse zone parents\n";
144 }
145
146 } elsif ($rec =~ /^C/) {
147 $cnt{CNAME}++;
148 my ($host,$targ,$ttl,$stamp,$loc) = split /:/, $rec, 5;
149 $host =~ s/^C//;
150 $host =~ s/\.$//;
151 $ttl = 0 if !$ttl;
152 $stamp = '' if !$stamp;
153 $loc = '' if !$loc;
154 $loc = '' if $loc =~ /^:+$/;
155 if ($host =~ /\.arpa$/) {
156 ($code,$msg) = DNSDB::_zone2cidr($host);
157 my ($rparent) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet >> ?", undef, ($msg));
158 $recsth->execute(0, $rparent, $targ, 5, $msg->addr, 0, 0, 0, $ttl);
159
160##fixme: automagically convert manually maintained sub-/24 delegations
161# my ($subip, $zone) = split /\./, $targ, 2;
162# ($code, $msg) = DNSDB::_zone2cidr($zone);
163# push @{$deleg{"$msg"}{iplist}}, $subip;
164#print "$msg $subip\n";
165
166 } else {
167 my $fparent = DNSDB::_hostparent($dbh, $host);
168 if ($fparent) {
169 $recsth->execute($fparent, 0, $host, 5, $targ, 0, 0, 0, $ttl);
170 } else {
171 push @deferred, $rec unless $nodefer;
172 # print "$tmporig deferred; can't find parent zone\n";
173 }
174 }
175
176 } elsif ($rec =~ /^\&/) {
177 $cnt{NS}++;
178 } elsif ($rec =~ /^\^/) {
179 $cnt{PTR}++;
180 } elsif ($rec =~ /^\+/) {
181 $cnt{A}++;
182 } elsif ($rec =~ /^Z/) {
183 $cnt{SOA}++;
184#Z128.91.209.in-addr.arpa:ns1.vianet.ca.:dnsadmin.vianet.ca.::1209600:1209600:900:900:900:
185 my ($zone,$master,$contact,$serial,$refresh,$retry,$expire,$minttl,$ttl,$stamp,$loc) = split /:/, $rec, 11;
186 $zone =~ s/^Z//;
187 $zone =~ s/\.$//;
188 $master =~ s/\.$//;
189 $contact =~ s/\.$//;
190 $ttl = 0 if !$ttl;
191 $stamp = '' if !$stamp;
192 $loc = '' if !$loc;
193 $loc = '' if $loc =~ /^:+$/;
194 if ($zone =~ /\.arpa$/) {
195 ($code,$msg) = DNSDB::_zone2cidr($zone);
196 $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,1,1)", undef, ($msg));
197 my ($rdns) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
198 $recsth->execute(0, $rdns, "$contact:$master", 6, "$refresh:$retry:$expire:$minttl", 0, 0, 0, $ttl);
199 } else {
200 $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,1,1)", undef, ($zone));
201 my ($domid) = $dbh->selectrow_array("SELECT currval('domains_domain_id_seq')");
202 $recsth->execute($domid, 0, "$contact:$master", 6, "$refresh:$retry:$expire:$minttl", 0, 0, 0, $ttl);
203 }
204 return; # since we don't use the "standard" record-add call at the bottom
205 } elsif ($rec =~ /^\@/) {
206 $cnt{MX}++;
207 } elsif ($rec =~ /^'/) {
208 $cnt{TXT}++;
209
210 my ($fqdn, $rdata, $ttl, $stamp, $loc) = split /:/, $rec, 5;
211 $fqdn =~ s/^'//;
212 _deoctal(\$rdata);
213 $ttl = 0 if !$ttl;
214 $stamp = '' if !$stamp;
215 $loc = '' if !$loc;
216 $loc = '' if $loc =~ /^:+$/;
217
218 my $domid = DNSDB::_hostparent($dbh, $fqdn);
219 if ($domid) {
220 $recsth->execute($domid, 0, $fqdn, 16, $rdata, 0, 0, 0, $ttl);
221 } else {
222 push @deferred, $rec unless $nodefer;
223 }
224
225 } elsif ($rec =~ /^\./) {
226 $cnt{NSASOA}++;
227 my ($fqdn, $ip, $ns, $ttl, $stamp, $loc) = split /:/, $rec, 6;
228 $fqdn =~ s/^\.//;
229 $fqdn =~ s/\.$//;
230 $ns =~ s/\.$//;
231 $ns = "$ns.ns.$fqdn" if $ns !~ /\./;
232 $ttl = 0 if !$ttl;
233 $stamp = '' if !$stamp;
234 $loc = '' if !$loc;
235 $loc = '' if $loc =~ /^:+$/;
236
237 if ($fqdn =~ /\.arpa$/) {
238 ($code,$msg) = DNSDB::_zone2cidr($fqdn);
239 my ($rdns) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet = ?", undef, ($msg));
240 if (!$rdns) {
241 $errstr = "adding revzone $msg";
242 $dbh->do("INSERT INTO revzones (revnet,group_id,status) VALUES (?,1,1)", undef, ($msg));
243 ($rdns) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
244# this would probably make a lot more sense to do hostmaster.$config{admindomain}
245 $recsth->execute(0, $rdns, "hostmaster.$fqdn:$ns", 6, "16384:2048:1048576:2560", 0, 0, 0, "2560");
246 }
247 $recsth->execute(0, $rdns, $ns, 2, "$msg", 0, 0, 0, $ttl);
248##fixme: (?) implement full conversion of tinydns . records?
249# -> problem: A record for NS must be added to the appropriate *forward* zone, not the reverse
250#$recsth->execute(0, $rdns, $ns, 1, $ip, 0, 0, 0, $ttl)
251# ... auto-A-record simply does not make sense in reverse zones. Functionally
252# I think it would work, sort of, but it's a nasty mess and anyone hosting reverse
253# zones has names for their nameservers already.
254# Even the auto-nameserver-fqdn comes out... ugly.
255
256##work
257 } else {
258 my ($domid) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE lower(domain) = lower(?)",
259 undef, ($fqdn));
260 if (!$domid) {
261 $errstr = "adding domain $fqdn";
262 $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,1,1)", undef, ($fqdn));
263 ($domid) = $dbh->selectrow_array("SELECT currval('domains_domain_id_seq')");
264 $recsth->execute($domid, 0, "hostmaster.$fqdn:$ns", 6, "16384:2048:1048576:2560", 0, 0, 0, "2560");
265 }
266 $recsth->execute($domid, 0, $fqdn, 2, $ns, 0, 0, 0, $ttl);
267 $recsth->execute($domid, 0, $ns, 1, $ip, 0, 0, 0, $ttl) if $ip;
268 }
269
270
271 } elsif ($rec =~ /^\%/) {
272 $cnt{VIEWS}++;
273 } elsif ($rec =~ /^:/) {
274 $cnt{NCUST}++;
275# Big section. Since tinydns can publish anything you can encode properly, but only provides official
276# recognition and handling for the core common types, this must deal with the leftovers.
277# :fqdn:type:rdata:ttl:time:loc
278#:mx2.sys.vianet.ca:44:\001\001\215\272\152\152\123\142\120\071\320\106\160\364\107\372\153\116\036\111\247\135:900::sys
279#:_sipfederationtls._tcp.ncstechnology.com:33:\000\144\000\001\023\305\006sipfed\006online\004lync\003com\000:3600::
280
281my (undef, $fqdn, $type, $rdata, $ttl, $time, $loc) = split /:/, $rec;
282
283if ($type == 33) {
284 # SRV
285 my ($prio, $weight, $port, $target) = (0,0,0,0);
286
287 my @tmp = _byteparse(\$rdata, 2);
288 $prio = $tmp[0] * 256 + $tmp[1];
289 @tmp = _byteparse(\$rdata, 2);
290 $weight = $tmp[0] * 256 + $tmp[1];
291 @tmp = _byteparse(\$rdata, 2);
292 $port = $tmp[0] * 256 + $tmp[1];
293
294 $rdata =~ s/\\\d{3}/./g;
295 ($target) = ($rdata =~ /^\.(.+)\.$/);
296# hmm. the above *should* work, but What If(TM) we have ASCII-range bytes
297# representing the target's fqdn part length(s)? axfr-get doesn't seem to,
298# probably because dec. 33->63 includes most punctuation and all the numbers
299# while ($rdata =~ /(\\\d{3})/) {
300# my $cnt = $1;
301# $rdata =~ s/^$cnt//;
302# $cnt =~ s/^\\/0/;
303# $cnt = oct($cnt);
304# my ($seg) = ($rdata =~ /^(.{$cnt})/);
305# $target .=
306# }
307
308 my $domid = DNSDB::_hostparent($dbh, $fqdn);
309 if ($domid) {
310 $recsth->execute($domid, 0, $fqdn, $type, $target, $prio, $weight, $port, $ttl) if $domid;
311 } else {
312 push @deferred, $rec unless $nodefer;
313 }
314
315} elsif ($type == 28) {
316 # AAAA
317
318 my @v6;
319
320 for (my $i=0; $i < 8; $i++) {
321 my @tmp = _byteparse(\$rdata, 2);
322 push @v6, sprintf("%0.4x", $tmp[0] * 256 + $tmp[1]);
323 }
324 my $val = NetAddr::IP->new(join(':', @v6));
325
326 my $domid = DNSDB::_hostparent($dbh, $fqdn);
327 if ($domid) {
328 $recsth->execute($domid, 0, $fqdn, $type, $val->addr, 0, 0, 0, $ttl) if $domid;
329 } else {
330 push @deferred, $rec unless $nodefer;
331 }
332
333} else {
334 # ... uhhh, dunno
335}
336
337 } else {
338 $cnt{other}++;
339 print " $_\n";
340 }
341 }
342
343 close FLAT;
344}
Note: See TracBrowser for help on using the repository browser.