source: trunk/compact-recs.pl@ 836

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

/trunk

Enhance compact-recs.pl with a --replace option for somewhat easier repeat calling

  • Property svn:executable set to *
  • Property svn:keywords set to Date Rev Author Id
File size: 9.5 KB
Line 
1#!/usr/bin/perl
2# Quick utility to use post-import to convert great huge piles of
3# A+PTR records to single A+PTR template records
4##
5# $Id: compact-recs.pl 836 2022-04-20 21:28:23Z kdeugau $
6# Copyright 2013-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
22use strict;
23use warnings;
24use Getopt::Long;
25
26# Taint-safe (ish) voodoo to push "the directory the script is in" into @INC.
27# See https://secure.deepnet.cx/trac/dnsadmin/ticket/80 for more gory details on how we got here.
28use File::Spec ();
29use File::Basename ();
30my $path;
31BEGIN {
32 $path = File::Basename::dirname(File::Spec->rel2abs($0));
33 if ($path =~ /(.*)/) {
34 $path = $1;
35 }
36}
37use lib $path;
38
39use DNSDB;
40
41usage() if !$ARGV[1];
42
43sub usage {
44 die qq(usage: compact-recs.pl netblock pattern [--replace [record id]]
45 netblock the CIDR block to define the A+PTR template on
46 pattern the pattern to define the new A+PTR template with, and
47 to match A+PTR records within the netblock for deletion
48 --replace Optional argument to update an existing template if found.
49 A record ID can be specified to match a particular record,
50 or 'all' to forcibly remove all but one in the event of
51 multiple records. If multiple records are found but neither
52 'all' or a specific record ID is specified, an error will be
53 returned and nothing will be changed since there is no
54 guarantee of which record might be replaced.
55
56 OR
57 compact-recs.pl --batch patternfile
58 patternfile should be a file containing a list of netblock-pattern
59 pairs, whitespace separated. --replace is ignored in this mode.
60
61 A PTR template record will be created instead of an A+PTR template
62 if the forward zone specified in the template is not present in
63 the database.
64
65 WARNING: Multiple runs may result in duplicate template records.
66);
67}
68
69my $batchmode = 0;
70my $replace = 0;
71my $tmpl_msg = '';
72
73GetOptions("batch" => \$batchmode,
74 "replace:s" => \&setreplace );
75
76sub setreplace {
77 if ($_[1] eq '') {
78 $replace = -1;
79 } else {
80 $replace = $_[1];
81 }
82}
83
84if ($replace && $replace !~ /^(all|-?\d+)$/) {
85 warn "Invalid --replace argument $replace:\n";
86 usage();
87}
88
89my $dnsdb = new DNSDB or die "Couldn't create DNSDB object: ".$DNSDB::errstr."\n";
90my $dbh = $dnsdb->{dbh};
91
92my $code;
93
94# get userdata for log
95($dnsdb->{logusername}, undef, undef, undef, undef, undef, $dnsdb->{logfullname}) = getpwuid($<);
96$dnsdb->{logfullname} =~ s/,//g;
97$dnsdb->{loguserid} = 0; # not worth setting up a pseudouser the way the RPC system does
98$dnsdb->{logusername} = $dnsdb->{logusername}."/compact-recs.pl";
99$dnsdb->{logfullname} = ($dnsdb->{logfullname} ? $dnsdb->{logfullname}."/compact-recs.pl" : $dnsdb->{logusername});
100
101if ($batchmode) {
102 # --replace not safe for --batch. could arguably support an in-file flag someday?
103 if ($replace) {
104 $replace = 0;
105 warn "--replace not compatible with --batch. Attempting to continue.\n";
106 }
107 open NBLIST, "<$ARGV[0]";
108 while (<NBLIST>) {
109 next if /^#/;
110 next if /^\s*$/;
111 s/^\s*//;
112 squashem(split(/\s+/));
113 }
114} else {
115 my $cidr = new NetAddr::IP $ARGV[0];
116 usage() if !$cidr;
117 squashem($cidr, $ARGV[1]);
118}
119
120exit 0;
121
122
123sub squashem {
124 my $cidr = shift;
125 my $patt = shift;
126
127 $dbh->{AutoCommit} = 0;
128 $dbh->{RaiseError} = 1;
129
130 my ($zonecidr,$zone,$ploc) = $dbh->selectrow_array(
131 "SELECT revnet,rdns_id,default_location FROM revzones WHERE revnet >>= ?",
132 undef, ($cidr) );
133 if (!$zone) {
134 warn "$cidr is not within a zone currently managed here.\n";
135 return;
136 }
137 my $soa = $dnsdb->getSOA('n', 'y', $zone);
138 my $dparent = $dnsdb->_hostparent($patt) || 0;
139 # Automatically choose new type as A+PTR template if the new pattern's
140 # domain is managed in this instance, or PTR template if not
141 my $newtype = ($dparent ? 65283 : 65282);
142
143 my ($tmplcount) = $dbh->selectrow_array("SELECT count(*) FROM records WHERE rdns_id = ? AND ".
144 "(type=65282 OR type=65283) AND val = ?", undef, ($zone, $cidr) );
145 if ($tmplcount && !$replace) {
146 # Template(s) found, --replace not set
147 print "One or more templates found for $cidr, use --replace [record_id],".
148 " --replace all, or clean up manually.\n";
149 return;
150 } elsif ($tmplcount > 1 && ($replace eq '0' || $replace eq '-1')) {
151 # Multiple templates found, --replace either not set (==0) or no argument provided (==-1)
152 print "Multiple templates found matching $cidr but no record ID specified with".
153 " --replace. Use --replace with a record ID, use --replace all, or clean up".
154 " manually.\n";
155 return;
156 }
157
158 print "Converting PTR and A+PTR records in $cidr matching $patt to single $typemap{$newtype} record\n";
159 my $delcnt = 0;
160
161 eval {
162 # First, clean up the records that match the template.
163 my $getsth = $dbh->prepare("SELECT record_id,host,val FROM records ".
164 "WHERE (type = 12 OR type > 65000) AND inetlazy(val) << ? AND rdns_id = ?");
165 my $delsth = $dbh->prepare("DELETE FROM records WHERE record_id = ?");
166 $getsth->execute($cidr, $zone);
167 my $i = 0;
168 while (my ($id,$host,$val) = $getsth->fetchrow_array) {
169 my $cmp = $patt;
170 DNSDB::_template4_expand(\$cmp, $val, \$cidr);
171 $delsth->execute($id) if $cmp eq $host;
172 $delcnt++ if $cmp eq $host;
173 }
174
175 my $template_modified = 0;
176
177 if ($replace) {
178 if ($replace eq 'all') {
179 # clear any templates with the same CIDR, and add a new one
180 $dbh->do("DELETE from records WHERE rdns_id = ? AND (type=65282 OR type=65283) AND val = ?", undef, ($zone, $cidr) );
181 $dbh->do("INSERT INTO records (domain_id, rdns_id, host, type, val, ttl, location) VALUES (?,?,?,?,?,?,?)",
182 undef, ($dparent, $zone, $patt, $newtype, $cidr, $soa->{minttl}, $ploc) );
183 $template_modified = 1;
184 $tmpl_msg = ", replaced $tmplcount template records";
185 } else {
186 if ($replace =~ /^\d+$/) {
187 # $replace == [id] -> replace that record ID, error if it doesn't exist or isn't
188 # a template for the specified CIDR. Arguably some stretch on the latter.
189 my ($rechost,$recval,$rectype) = $dbh->selectrow_array(
190 "SELECT host,val,type,record_id FROM records WHERE record_id = ?",
191 undef, $replace);
192 if (($rectype == 65282 || $rectype == 65283) && $recval eq $cidr) {
193 # Do the update if the record specified matches is a suitable template
194 $dbh->do("UPDATE records SET host = ?, type = ?, val = ? WHERE record_id = ?",
195 undef, ($patt, $newtype, $cidr, $replace) );
196 $template_modified = 1;
197 $tmpl_msg = ", replaced an existing template record";
198 } else {
199 # Specified record ID isn't a template record, or doesn't match $cidr, or both
200 die "Specified record ID isn't a template for $cidr, skipping:\n".
201 " $replace found: $rechost $typemap{$rectype} $recval\n";
202 }
203 } else {
204 # $replace == -1 -> replace/update template iff one template is present
205 # (should have errored out earlier if multiple templates are present)
206 my ($rechost,$recval,$rectype,$recid) = $dbh->selectrow_array(
207 "SELECT host,val,type,record_id FROM records WHERE rdns_id = ? AND (type=65282 OR type=65283) AND val = ?",
208 undef, ($zone, $cidr) );
209 if ($recid) {
210 # Do the update if we've found an existing template with the same CIDR
211 $dbh->do("UPDATE records SET host = ?, type = ?, val = ? WHERE record_id = ?",
212 undef, ($patt, $newtype, $cidr, $recid) );
213 $template_modified = 1;
214 $tmpl_msg = ", replaced an existing template record";
215 } else {
216 $dbh->do("INSERT INTO records (domain_id, rdns_id, host, type, val, ttl, location) VALUES (?,?,?,?,?,?,?)",
217 undef, ($dparent, $zone, $patt, $newtype, $cidr, $soa->{minttl}, $ploc) );
218 $template_modified = 1;
219 }
220 } # $replace -> [id] or $replace == -1
221 } # $replace <> 'all'
222 } else {
223 # $replace == 0 (not set), just insert the new template
224 $dbh->do("INSERT INTO records (domain_id, rdns_id, host, type, val, ttl, location) VALUES (?,?,?,?,?,?,?)",
225 undef, ($dparent, $zone, $patt, $newtype, $cidr, $soa->{minttl}, $ploc) );
226 $template_modified = 1;
227 }
228
229 if ($template_modified) {
230 my %logdata = (rdns_id => $zone, domain_id => $dparent, group_id => 1,
231 entry => "A+PTR and/or PTR records in $cidr matching $patt replaced by $typemap{$newtype} record for $cidr");
232 $dnsdb->_updateserial(%logdata);
233 $dnsdb->_log(%logdata);
234 $dbh->commit;
235 } else {
236 # no need to do push out a null update that just bumps the serial on the zone(s)
237 $dbh->rollback;
238 }
239
240 };
241 if ($@) {
242 print "Error(s) encountered: $@\n";
243 $dbh->rollback;
244 return;
245 }
246 print " complete (removed $delcnt PTR/A+PTR records";
247 print $tmpl_msg;
248 print ")\n";
249} # squashem ()
Note: See TracBrowser for help on using the repository browser.