#!/usr/bin/perl # Quick utility to use post-import to convert great huge piles of # A+PTR records to single A+PTR template records ## # $Id: compact-recs.pl 834 2022-04-19 20:34:34Z kdeugau $ # Copyright 2013-2022 Kris Deugau # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ## use strict; use warnings; # Taint-safe (ish) voodoo to push "the directory the script is in" into @INC. # See https://secure.deepnet.cx/trac/dnsadmin/ticket/80 for more gory details on how we got here. use File::Spec (); use File::Basename (); my $path; BEGIN { $path = File::Basename::dirname(File::Spec->rel2abs($0)); if ($path =~ /(.*)/) { $path = $1; } } use lib $path; use DNSDB; usage() if !$ARGV[1]; sub usage { die qq(usage: compact-recs.pl netblock pattern netblock the CIDR block to define the A+PTR template on pattern the pattern to define the new A+PTR template with, and to match A+PTR records within the netblock for deletion OR compact-recs.pl --batch patternfile patternfile should be a file containing a list of netblock-pattern pairs, whitespace separated A PTR template record will be created instead of an A+PTR template if the forward zone specified in the template is not present in the database. WARNING: Multiple runs will result in duplicate template records. ); } my $dnsdb = new DNSDB or die "Couldn't create DNSDB object: ".$DNSDB::errstr."\n"; my $dbh = $dnsdb->{dbh}; my $code; # get userdata for log ($dnsdb->{logusername}, undef, undef, undef, undef, undef, $dnsdb->{logfullname}) = getpwuid($<); $dnsdb->{logfullname} =~ s/,//g; $dnsdb->{loguserid} = 0; # not worth setting up a pseudouser the way the RPC system does $dnsdb->{logusername} = $dnsdb->{logusername}."/compact-recs.pl"; $dnsdb->{logfullname} = $dnsdb->{logusername} if !$dnsdb->{logfullname}; if ($ARGV[0] eq '--batch') { open NBLIST, "<$ARGV[1]"; while () { next if /^#/; next if /^\s*$/; s/^\s*//; squashem(split(/\s+/)); } } else { my $cidr = new NetAddr::IP $ARGV[0]; usage() if !$cidr; squashem($cidr, $ARGV[1]); } exit 0; sub squashem { my $cidr = shift; my $patt = shift; $dbh->{AutoCommit} = 0; $dbh->{RaiseError} = 1; my ($zonecidr,$zone,$ploc) = $dbh->selectrow_array("SELECT revnet,rdns_id,default_location FROM revzones WHERE revnet >>= ?", undef, ($cidr) ); if (!$zone) { warn "$cidr is not within a zone currently managed here.\n"; return; } my $soa = $dnsdb->getSOA('n', 'y', $zone); my $dparent = $dnsdb->_hostparent($patt) || 0; my $newtype = ($dparent ? 65283 : 65282); my ($istmpl) = $dbh->selectrow_array("SELECT count(*) FROM records WHERE rdns_id = ? AND ". "(type=65282 OR type=65283) AND val = ?", undef, ($zone, $cidr) ); if ($istmpl) { print "Template already exists for $cidr, manual cleanup required\n"; return; } print "Converting PTR and A+PTR records in $cidr matching $patt to single $typemap{$newtype} record\n"; my $delcnt = 0; eval { # First, clean up the records that match the template. my $getsth = $dbh->prepare("SELECT record_id,host,val FROM records ". "WHERE (type = 12 OR type > 65000) AND inetlazy(val) << ? AND rdns_id = ?"); my $delsth = $dbh->prepare("DELETE FROM records WHERE record_id = ?"); $getsth->execute($cidr, $zone); my $i = 0; while (my ($id,$host,$val) = $getsth->fetchrow_array) { my $cmp = $patt; DNSDB::_template4_expand(\$cmp, $val, \$cidr); $delsth->execute($id) if $cmp eq $host; $delcnt++ if $cmp eq $host; } $dbh->do("INSERT INTO records (domain_id, rdns_id, host, type, val, ttl, location) VALUES (?,?,?,?,?,?,?)", undef, ($dparent, $zone, $patt, $newtype, $cidr, $soa->{minttl}, $ploc) ); $dbh->do("UPDATE revzones SET changed='y' WHERE rdns_id = ?", undef, ($zone)); $dbh->do("UPDATE domains SET changed='y' WHERE domain_id = ?", undef, ($dparent)) if $dparent; $dnsdb->_log(rdns_id => $zone, domain_id => $dparent, group_id => 1, entry => "A+PTR and/or PTR records in $cidr matching $patt replaced by $typemap{$newtype} record for $cidr"); $dbh->commit; }; if ($@) { print "barf: $@\n"; $dbh->rollback; return; } print " complete ($delcnt records)\n"; } # squashem ()