source: trunk/DNSDB/ExportBIND.pm@ 871

Last change on this file since 871 was 871, checked in by Kris Deugau, 2 years ago

/trunk

BIND export, unwinding dev saves, 23 of many many

  • Accumulated cleanup for main reverse zone export loop; in looking at the copy-paste-edit to add the domain export loop, the committed code got badly out of sync with the dev save's complete loop.
  • Property svn:keywords set to Date Rev Author Id
File size: 21.6 KB
Line 
1# dns/trunk/DNSDB/ExportBIND.pm
2# BIND data export/publication
3# Call through DNSDB.pm's export() sub
4##
5# $Id: ExportBIND.pm 871 2022-09-28 21:51:26Z kdeugau $
6# Copyright 2022 Kris Deugau <kdeugau@deepnet.cx>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20##
21
22package DNSDB::ExportBIND;
23
24use strict;
25use warnings;
26
27sub export {
28 # expected to be a DNSDB object
29 my $dnsdb = shift;
30
31 # to be a hash of views/locations, containing lists of zones
32 my %viewzones;
33
34 # allow for future exports of subgroups of records
35 my $viewlist = $dnsdb->getLocList(curgroup => 1);
36
37
38## export reverse zones
39
40 my $soasth = $dnsdb->{dbh}->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location ".
41 "FROM records WHERE rdns_id=? AND type=6");
42 my $recsth = $dnsdb->{dbh}->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location,extract(epoch from stamp),expires,stampactive ".
43 "FROM records WHERE rdns_id=? AND NOT type=6 ".
44 "ORDER BY masklen(inetlazy(val)) DESC, inetlazy(val)");
45
46 # Fetch active zone list
47 my $revsth = $dnsdb->{dbh}->prepare("SELECT rdns_id,revnet,status,changed,default_location FROM revzones WHERE status=1 ".
48 "ORDER BY masklen(revnet),revnet DESC, rdns_id");
49 # Unflag changed zones, so we can maybe cache the export and not redo everything every time
50 my $zonesth = $dnsdb->{dbh}->prepare("UPDATE revzones SET changed='n' WHERE rdns_id=?");
51 $revsth->execute();
52
53 my %recflags; # need this to be independent for forward vs reverse zones, as they're not merged
54
55 while (my ($revid,$revzone,$revstat,$changed,$defloc) = $revsth->fetchrow_array) {
56 my $cidr = NetAddr::IP->new($revzone);
57 my $zfile = $cidr->network->addr."-".$cidr->masklen;
58# my $cachefile = "$dnsdb->{exportcache}/$zfile";
59# my $tmpcache = "$dnsdb->{exportcache}/tmp.$zfile.$$";
60 my $tmpcache = "tmp.$zfile.$$"; # safety net. don't overwrite a previous known-good file
61
62##fixme: convert logical revzone into .arpa name? maybe take a slice of showrev_arpa?
63##fixme: need to bodge logical non-octet-boundary revzones into octet-boundary revzones
64##fixme: do we do cache files? views balloon the file count stupidly
65## foreach $octetzone $cidr->split(octet-boundary)
66## loclist = SELECT DISTINCT location FROM records WHERE rdns_id = $zid AND inetlazy(val) <<= $octetzone
67
68#printf "non-octet? %s, %i\n", $cidr->masklen, $cidr->masklen % 8;
69
70 # fetch a list of views/locations present in the zone. we need to publish a file for each one.
71 # in the event that no locations are present (~~ $viewlist is empty), /%view collapses to nothing in the zone path
72# my (@loclist) = $dnsdb->{dbh}->selectrow_array("SELECT DISTINCT location FROM records WHERE rdns_id = ?", undef, $revid);
73 my $tmplocs = $dnsdb->{dbh}->selectall_arrayref("SELECT DISTINCT location FROM records WHERE rdns_id = ?", undef, $revid);
74 my @loclist;
75 foreach my $tloc (@{$tmplocs}) {
76 push @loclist, ($tloc->[0] eq '' ? 'common' : $tloc->[0]);
77 }
78
79 my %zonefiles; # zone file handles
80
81 eval {
82
83 my $arpazone = DNSDB::_ZONE($cidr, 'ZONE', 'r', '.').($cidr->{isv6} ? '.ip6.arpa' : '.in-addr.arpa');
84
85##fixme: need to open separate zone files for aggregated metazones eg /22 or /14
86 foreach my $loc (@loclist) {
87 my $zfilepath = $dnsdb->bind_export_reverse_zone_path};
88 $zfilepath =~ s/\%view/$loc/;
89 $zfilepath =~ s/\%zone/$revzone/;
90 $zfilepath =~ s/\%arpazone/$arpazone/;
91
92 # Just In Case(TM)
93 $zfilepath =~ s,[^\w./-],_,g;
94
95# open $zonefiles{$loc}, ">", $zfilepath;
96
97 # write fresh records if:
98 # - we are not using the cache
99 # - force_refresh is set
100 # - the zone has changed
101 # - the cache file does not exist
102 # - the cache file is empty
103 if ($dnsdb->{force_refresh} || $changed || !-e $zfilepath || -z $zfilepath) {
104# if (!$dnsdb->{usecache} || $dnsdb->{force_refresh} || $changed || !-e $cachefile || -z $cachefile) {
105# if ($dnsdb->{usecache}) {
106# open ZONECACHE, ">$tmpcache" or die "Error creating temporary file $tmpcache: $!\n";
107# $zonefilehandle = *ZONECACHE;
108# }
109 open $zonefiles{$loc}, ">", $zfilepath or die "Error creating temporary file $zfilepath: $!\n";
110
111 printf {$zonefiles{$loc}} "; %s in view %s exported %s\n", $arpazone, $loc, scalar(localtime)
112 or die "Error writing header [$zone, '$loc']: $!\n";;
113
114 # need to fetch this separately since the rest of the records all (should) have real IPs in val
115 $soasth->execute($revid);
116 my (@zsoa) = $soasth->fetchrow_array();
117##fixme: do we even need @loclist passed in?
118 publishrec_bind(\%zonefiles, \@loclist, $zsoa[7], 'y', \%recflags, $revzone,
119 $zsoa[0], $zsoa[1], $zsoa[2], $zsoa[3], $zsoa[4], $zsoa[5], $zsoa[6], $loc, '');
120 } # if force_refresh etc
121
122 # tag the zonefile for publication in the view
123 push @{$viewzones{$loc}}, $arpazone;
124 } # foreach @loclist
125
126 # now the meat of the records
127 $recsth->execute($revid);
128 my $fullzone = _ZONE($tmpzone, 'ZONE', 'r', '.').($tmpzone->{isv6} ? '.ip6.arpa' : '.in-addr.arpa');
129
130 while (my ($host, $type, $val, $dist, $weight, $port, $ttl, $recid, $loc, $stamp, $expires, $stampactive)
131 = $recsth->fetchrow_array) {
132 next if $recflags{$recid};
133
134 # Check for out-of-zone data
135 if ($val =~ /\.arpa$/) {
136 # val is non-IP
137 if ($val !~ /$fullzone$/) {
138 warn "Not exporting out-of-zone record $val $typemap{$type} $host, $ttl (zone $tmpzone)\n";
139 next;
140 }
141 } else {
142 my $ipval = new NetAddr::IP $val;
143 if (!$tmpzone->contains($ipval)) {
144 warn "Not exporting out-of-zone record $val $typemap{$type} $host, $ttl (zone $tmpzone)\n";
145 next;
146 }
147 } # is $val a raw .arpa name?
148
149 # Spaces are evil.
150 $val =~ s/^\s+//;
151 $val =~ s/\s+$//;
152 if ($typemap{$type} ne 'TXT') {
153 # Leading or trailng spaces could be legit in TXT records.
154 $host =~ s/^\s+//;
155 $host =~ s/\s+$//;
156 }
157
158 publishrec_bind(\%zonefiles, \@loclist, $recid, 'y', \%recflags, $revzone,
159 $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp, $expires, $stampactive);
160
161 $recflags{$recid} = 1;
162
163 } # while ($recsth)
164
165# if ($dnsdb->{usecache}) {
166# close ZONECACHE; # force the file to be written
167# # catch obvious write errors that leave an empty temp file
168# if (-s $tmpcache) {
169# rename $tmpcache, $cachefile
170# or die "Error overwriting cache file $cachefile with temporary file: $!\n";
171# }
172# }
173
174 };
175 if ($@) {
176 die "error writing ".($dnsdb->{usecache} ? 'new data for ' : '')."$revzone: $@\n";
177 # error! something borked, and we should be able to fall back on the old cache file
178 # report the error, somehow.
179 } else {
180 # mark zone as unmodified. Only do this if no errors, that way
181 # export failures should recover a little more automatically.
182 $zonesth->execute($revid);
183 }
184
185# if ($dnsdb->{usecache}) {
186# # We've already made as sure as we can that a cached zone file is "good",
187# # although possibly stale/obsolete due to errors creating a new one.
188# eval {
189# open CACHE, "<$cachefile" or die $!;
190# print $datafile $_ or die "error copying cached $revzone to master file: $!" while <CACHE>;
191# close CACHE;
192# };
193# die $@ if $@;
194# }
195
196 } # revsth->fetch
197
198
199
200 # Write the view configuration last, because otherwise we have to be horribly inefficient
201 # at figuring out which zones are visible/present in which views
202 if ($viewlist) {
203 my $tmpconf = "$dnsdb->{bind_zone_conf}.$$"; ##fixme: split filename for prefixing
204 open BINDCONF, ">", $tmpconf;
205
206 foreach my $view (@{$viewlist}, 'common') {
207#print Dumper($view);
208 print BINDCONF "view $view->{location} {\n";
209# print "view $view->{location} {\n";
210 # could also use an acl { ... }; statement, then match-clients { aclname; };, but that gets hairy
211 # note that some semantics of data visibility need to be handled by the record export, since it's
212 # not 100% clear if the semantics of a tinydns view with an empty IP list (matches anyone) are the
213 # same as a BIND view with match-clients { any; };
214 if ($view->{iplist}) {
215 print BINDCONF " match-clients { ".join("; ", $view->{iplist})."; };\n";
216# print " match-clients { ".join("; ", split(/[\s,]+/, $view->{iplist}))."; };\n";
217 } else {
218 print BINDCONF " match-clients { any; };\n";
219# print " match-clients { any; };\n";
220 }
221 foreach my $zone (@{$viewzones{$view->{location}}}) {
222##fixme: notify settings, maybe per-zone?
223 print qq( zone "$zone" IN {\n\ttype master;\n\tnotify no;\n\tfile "db.$zone";\n };\n);
224 }
225 print BINDCONF "};\n\n";
226 print "};\n\n";
227 } # foreach @$viewlist
228 rename $tmpconf, $dnsdb->{bind_zone_conf};
229 } # if $viewlist
230
231} # export()
232
233
234# Print individual records in BIND format
235sub publishrec_bind {
236 my $dnsdb = shift;
237
238# my ($zonefiles, $recid, $revrec, $loclist, $zone, $host, $type, $val, $distance, $weight, $port, $ttl,
239 my ($zonefiles, $loclist, $recid, $revrec, $recflags, $zone, $host, $type, $val, $distance, $weight, $port, $ttl,
240 $loc, $stamp, $expires, $stampactive) = @_;
241
242# make sure "global" records get into all the right per-view zone files, without having to do this loop in each record-print location
243##fixme: maybe exclude the template types? those may be more expensive to export
244## *ponder* may be more efficient to loop in each record print due to substitution and manipulation from stored data to formal
245## record for .arpa zones for all records
246 if ($loc eq '') {
247 foreach my $subloc (@{$loclist}) {
248 publishrec_bind($zonefiles, $loclist, $recid, $revrec, $recflags, $zone, $host, $type, $val, $distance, $weight, $port, $ttl,
249 $subloc, $stamp, $expires, $stampactive);
250 }
251 }
252
253 # Just In Case something is lingering in the DB
254 $loc = '' if !$loc;
255
256 ## And now to the records!
257
258 if ($typemap{$type} eq 'SOA') {
259 # host contains pri-ns:responsible
260 # val is abused to contain refresh:retry:expire:minttl
261 # let's be explicit about abusing $host and $val
262 my ($email, $primary) = (split /:/, $host)[0,1];
263 my ($refresh, $retry, $expire, $min_ttl) = (split /:/, $val)[0,1,2,3];
264 my $serial = 0; # fail less horribly than leaving it empty?
265 # just snarfing the right SOA serial for the zone type
266 if ($revrec eq 'y') {
267 ($serial) = $dnsdb->{dbh}->selectrow_array("SELECT zserial FROM revzones WHERE revnet=?", undef, $zone);
268 } else {
269 ($serial) = $dnsdb->{dbh}->selectrow_array("SELECT zserial FROM domains WHERE domain=?", undef, $zone);
270 } # revrec <> 'y'
271 # suppress a "uninitialized value" warning. should be impossible but...
272 # abuse hours as the last digit pair of the serial for simplicity
273##fixme?: alternate SOA serial schemes?
274 $serial = strftime("%Y%m%d%H", localtime()) if !$serial;
275 $primary .= "." if $primary !~ /\.$/;
276 $email .= "." if $email !~ /\.$/;
277# print *{$zonefiles->{$loc}} "Z$zone:$primary:$email:$serial:$refresh:$retry:$expire:$min_ttl:$ttl:$stamp:$loc\n"
278# or die $!;
279# print *{$zonefiles->{$loc}} "$zone $ttl IN SOA $primary $email ( $serial $refresh $retry $expire $min_ttl )\n"
280# or die "couldn't write $zone SOA: $!";
281 my $recdata = "$zone $ttl IN SOA $primary $email ( $serial $refresh $retry $expire $min_ttl )\n";
282 recprint($zonefiles, $loclist, $loc, $recdata);
283 } # SOA
284
285 elsif ($typemap{$type} eq 'A') {
286# ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
287# print $datafile "+$host:$val:$ttl:$stamp:$loc\n" or die $!;
288# print {$zonefiles->{$loc}} "$host $ttl IN A $val\n" or die $!;
289 my $recdata = "$host $ttl IN A $val\n";
290 recprint($zonefiles, $loclist, $loc, $recdata);
291 } # A
292
293 elsif ($typemap{$type} eq 'NS') {
294 if ($revrec eq 'y') {
295 $val = NetAddr::IP->new($val);
296
297##fixme: conversion for sub-/24 delegations in reverse zones?
298# if (!$val->{isv6} && ($val->masklen > 24)) {
299# }
300
301# print {$zonefiles->{$loc}} "$zone $ttl IN NS $host\n";
302# print "$zone $ttl IN NS $host\n" or die $!;
303 my $recdata = "$zone $ttl IN NS $host\n";
304 recprint($zonefiles, $loclist, $loc, $recdata);
305
306 } else {
307# print $datafile "\&$host"."::$val:$ttl:$stamp:$loc\n" or die $!;
308 }
309 } # NS
310
311 elsif ($typemap{$type} eq 'AAAA') {
312# ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
313# print {$zonefiles->{$loc}} "$host $ttl IN AAAA $val\n" or die $!;
314 my $recdata = "$host $ttl IN AAAA $val\n";
315 recprint($zonefiles, $loclist, $loc, $recdata);
316 } # AAAA
317
318 elsif ($typemap{$type} eq 'TXT') {
319# ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
320# print {$zonefiles->{$loc}} "$host $ttl IN TXT \"$val\"\n" or die $!;
321 my $recdata = "$host $ttl IN TXT \"$val\"\n";
322 recprint($zonefiles, $loclist, $loc, $recdata);
323 } # TXT
324
325 elsif ($typemap{$type} eq 'CNAME') {
326# ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
327# print {$zonefiles->{$loc}} "$host $ttl IN CNAME $val\n" or die $!;
328 my $recdata = "$host $ttl IN CNAME $val\n";
329 recprint($zonefiles, $loclist, $loc, $recdata);
330 } # CNAME
331
332 elsif ($typemap{$type} eq 'SRV') {
333# ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
334# print {$zonefiles->{$loc}} "$host $ttl IN SRV $distance $weight $port $val\n" or die $!;
335 my $recdata = "$host $ttl IN SRV $distance $weight $port $val\n";
336 recprint($zonefiles, $loclist, $loc, $recdata);
337 } # SRV
338
339 elsif ($typemap{$type} eq 'RP') {
340# ($host,$val) = __revswap($host,$val) if $revrec eq 'y';
341# print {$zonefiles->{$loc}} "$host $ttl IN RP $val\n" or die $!;
342 my $recdata = "$host $ttl IN RP $val\n";
343 recprint($zonefiles, $loclist, $loc, $recdata);
344 } # RP
345
346
347 elsif ($typemap{$type} eq 'PTR') {
348 $$recflags{$val}++;
349 if ($revrec eq 'y') {
350
351 if ($val =~ /\.arpa$/) {
352 # someone put in the formal .arpa name. humor them.
353# print {$zonefiles->{$loc}} "$val $ttl IN PTR $host\n" or die $!;
354 my $recdata = "$val $ttl IN PTR $host\n";
355 recprint($zonefiles, $loclist, $loc, $recdata);
356 } else {
357 $zone = NetAddr::IP->new($zone);
358 if (!$zone->{isv6} && $zone->masklen > 24) {
359 # sub-octet v4 zone
360 ($val) = ($val =~ /\.(\d+)$/);
361# print {$zonefiles->{$loc}} "$val.".DNSDB::_ZONE($zone, 'ZONE', 'r', '.').'.in-addr.arpa'.
362# " $ttl IN PTR $host\n"
363# or die $!;
364 my $recdata = "$val.".DNSDB::_ZONE($zone, 'ZONE', 'r', '.').".in-addr.arpa $ttl IN PTR $host\n";
365 recprint($zonefiles, $loclist, $loc, $recdata);
366 } else {
367 # not going to care about strange results if $val is not an IP value and is resolveable in DNS
368 $val = NetAddr::IP->new($val);
369# print {$zonefiles->{$loc}} DNSDB::_ZONE($val, 'ZONE', 'r', '.').($val->{isv6} ? '.ip6.arpa' : '.in-addr.arpa').
370# " $ttl IN PTR $host\n"
371# or die $!;
372 my $recdata = DNSDB::_ZONE($val, 'ZONE', 'r', '.').($val->{isv6} ? '.ip6.arpa' : '.in-addr.arpa').
373 " $ttl IN PTR $host\n";
374 recprint($zonefiles, $loclist, $loc, $recdata);
375 }
376 } # non-".arpa" $val
377
378 } else {
379 # PTRs in forward zones are less bizarre and insane than some other record types
380 # in reverse zones... OTOH we can't validate them any which way, so we cross our
381 # fingers and close our eyes and make it Someone Else's Problem.
382# print {$zonefiles->{$loc}} "$host $ttl IN PTR $val\n" or die $!;
383 my $recdata = "$host $ttl IN PTR $val\n";
384 recprint($zonefiles, $loclist, $loc, $recdata);
385 }
386 } # PTR
387
388 elsif ($type == 65280) { # A+PTR
389 # Recurse to PTR or A as appropriate because BIND et al don't share
390 # the tinydns concept of merged forward/reverse records
391 $$recflags{$val}++;
392 if ($revrec eq 'y') {
393 publishrec_bind($zonefiles, $loclist, $recid, $revrec, $recflags, $zone, $host, 12, $val, $distance, $weight, $port, $ttl,
394 $loc, $stamp, $expires, $stampactive);
395#print {$zonefiles->{$loc}} "=$host:$val:$ttl:$stamp:$loc\n" or die $!;
396# publishrec_bind(\%zonefiles, $recid, 'y', \@loclist, $revzone,
397# $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp, $expires, $stampactive);
398# my ($zonefiles, $recid, $revrec, $loclist, $zone, $host, $type, $val, $distance, $weight, $port, $ttl,
399# $loc, $stamp, $expires, $stampactive) = @_;
400 } else {
401 publishrec_bind($zonefiles, $loclist, $recid, $revrec, $recflags, $zone, $host, 1, $val, $distance, $weight, $port, $ttl,
402 $loc, $stamp, $expires, $stampactive);
403 }
404 } # A+PTR
405
406 elsif ($type == 65282) { # PTR template
407 # only useful for v4 with standard DNS software, since this expands all
408 # IPs in $zone (or possibly $val?) with autogenerated records
409 $val = NetAddr::IP->new($val);
410 return if $val->{isv6};
411
412 if ($val->masklen <= 16) {
413 foreach my $sub ($val->split(16)) {
414 __publish_template_bind($sub, $recflags, $host, $zonefiles, $loclist, $ttl, $stamp, $loc, $zone, $revrec);
415 }
416 } else {
417 __publish_template_bind($sub, $recflags, $host, $zonefiles, $loclist, $ttl, $stamp, $loc, $zone, $revrec);
418 }
419 } # PTR template
420
421 elsif ($type == 65283) { # A+PTR template
422 $val = NetAddr::IP->new($val);
423 # Just In Case. An A+PTR should be impossible to add to a v6 revzone via API.
424 return if $val->{isv6};
425
426 if ($val->masklen < 16) {
427 foreach my $sub ($val->split(16)) {
428 __publish_template_bind($sub, $recflags, $host, $zonefiles, $loclist, $ttl, $stamp, $loc, $zone, $revrec);
429 }
430 } else {
431 __publish_template_bind($sub, $recflags, $host, $zonefiles, $loclist, $ttl, $stamp, $loc, $zone, $revrec);
432 }
433 } # A+PTR template
434
435 elsif ($type == 65284) { # AAAA+PTR template
436 # Stub for completeness. Could be exported to DNS software that supports
437 # some degree of internal automagic in generic-record-creation
438 # (eg http://search.cpan.org/dist/AllKnowingDNS/ )
439 } # AAAA+PTR template
440
441} # publishrec_bind()
442
443
444sub __publish_template_bind {
445 my $sub = shift;
446 my $recflags = shift;
447 my $hpat = shift;
448 my $zonefiles = shift;
449 my $loclist = shift;
450 my $ttl = shift;
451 my $stamp = shift;
452 my $loc = shift;
453 my $zone = new NetAddr::IP shift;
454 my $revrec = shift || 'y';
455# my $ptrflag = shift || 0; ##fixme: default to PTR instead of A record for the BIND variant of this sub?
456
457 # do this conversion once, not (number-of-ips-in-subnet) times
458 my $arpabase = DNSDB::_ZONE($zone, 'ZONE.in-addr.arpa', 'r', '.');
459
460 my $iplist = $sub->splitref(32);
461 my $ipindex = -1;
462 foreach (@$iplist) {
463 my $ip = $_->addr;
464 $ipindex++;
465 # make as if we split the non-octet-aligned block into octet-aligned blocks as with SOA
466 my $lastoct = (split /\./, $ip)[3];
467
468 # Allow smaller entries to override longer ones, eg, a specific PTR will
469 # always publish, overriding any template record containing that IP.
470 # %blank% also needs to be per-IP here to properly cascade overrides with
471 # multiple nested templates
472 next if $$recflags{$ip}; # && $self->{skip_bcast_255}
473 $$recflags{$ip}++;
474 next if $hpat eq '%blank%';
475
476 my $rec = $hpat; # start fresh with the template for each IP
477##fixme: there really isn't a good way to handle sub-/24 zones here. This way at least
478# seems less bad than some alternatives.
479 $dnsdb->_template4_expand(\$rec, $ip, \$sub, $ipindex);
480 # _template4_expand may blank $rec; if so, don't publish a record
481 next if !$rec;
482##fixme: trim merged record type voodoo. "if ($ptrflag) {} else {}" ?
483# if ($ptrflag || $zone->masklen > 24) {
484 my $recdata;
485 if ($revrec eq 'y') {
486# || $zone->masklen > 24) {
487# print $fh "^$lastoct.$arpabase:$rec:$ttl:$stamp:$loc\n" or die $!;
488##fixme: use $ORIGIN instead? make the FQDN output switchable-optional?
489# print $fh "$lastoct.$arpabase $ttl IN PTR $rec\n" or die $!;
490# if ($revrec ne 'y') {
491 # print a separate A record. Arguably we could use an = record here instead.
492# print $fh "+$rec:$ip:$ttl:$stamp:$loc\n" or die $!;
493# print $fh "$rec $ttl IN A $ip\n" or die $!;
494# }
495 $recdata = "$lastoct.$arpabase $ttl IN PTR $rec\n";
496 } else {
497 # A record, not merged
498# print $fh "=$rec:$ip:$ttl:$stamp:$loc\n" or die $!;
499# print $fh "$rec $ttl IN A $ip\n" or die $!;
500 $recdata = "$rec $ttl IN A $ip\n";
501 }
502 # and finally
503 recprint($zonefiles, $loclist, $loc, $recdata);
504 } # foreach (@iplist)
505} # __publish_template_bind()
506
507
508# actual record printing sub
509# loop on the locations here so we don't end up with a huge pot of copypasta
510sub recprint {
511 my ($zonefiles, $loclist, $loc, $recdata) = @_;
512 if ($loc eq '') {
513 # "common" record visible in all locations
514 foreach my $rloc (@{$loclist}) {
515 print {$zonefiles->{$rloc}} $recdata or die $!;
516 }
517 } else {
518 # record with specific location tagged
519 print {$zonefiles->{$loc}} $recdata or die $!;
520 }
521}
522
5231;
Note: See TracBrowser for help on using the repository browser.