source: trunk/compact-recs.pl@ 869

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

/trunk

Minor refinement to eliminate an error message from compact-recs.pl

  • Property svn:executable set to *
  • Property svn:keywords set to Date Rev Author Id
File size: 9.6 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 852 2022-09-15 18:59:56Z 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 # skip existing template within the new template's range
171 next if $val =~ m{/\d+$};
172 DNSDB::_template4_expand(\$cmp, $val, \$cidr);
173 $delsth->execute($id) if $cmp eq $host;
174 $delcnt++ if $cmp eq $host;
175 }
176
177 my $template_modified = 0;
178
179 if ($replace) {
180 if ($replace eq 'all') {
181 # clear any templates with the same CIDR, and add a new one
182 $dbh->do("DELETE from records WHERE rdns_id = ? AND (type=65282 OR type=65283) AND val = ?", undef, ($zone, $cidr) );
183 $dbh->do("INSERT INTO records (domain_id, rdns_id, host, type, val, ttl, location) VALUES (?,?,?,?,?,?,?)",
184 undef, ($dparent, $zone, $patt, $newtype, $cidr, $soa->{minttl}, $ploc) );
185 $template_modified = 1;
186 $tmpl_msg = ", replaced $tmplcount template records";
187 } else {
188 if ($replace =~ /^\d+$/) {
189 # $replace == [id] -> replace that record ID, error if it doesn't exist or isn't
190 # a template for the specified CIDR. Arguably some stretch on the latter.
191 my ($rechost,$recval,$rectype) = $dbh->selectrow_array(
192 "SELECT host,val,type,record_id FROM records WHERE record_id = ?",
193 undef, $replace);
194 if (($rectype == 65282 || $rectype == 65283) && $recval eq $cidr) {
195 # Do the update if the record specified matches is a suitable template
196 $dbh->do("UPDATE records SET host = ?, type = ?, val = ? WHERE record_id = ?",
197 undef, ($patt, $newtype, $cidr, $replace) );
198 $template_modified = 1;
199 $tmpl_msg = ", replaced an existing template record";
200 } else {
201 # Specified record ID isn't a template record, or doesn't match $cidr, or both
202 die "Specified record ID isn't a template for $cidr, skipping:\n".
203 " $replace found: $rechost $typemap{$rectype} $recval\n";
204 }
205 } else {
206 # $replace == -1 -> replace/update template iff one template is present
207 # (should have errored out earlier if multiple templates are present)
208 my ($rechost,$recval,$rectype,$recid) = $dbh->selectrow_array(
209 "SELECT host,val,type,record_id FROM records WHERE rdns_id = ? AND (type=65282 OR type=65283) AND val = ?",
210 undef, ($zone, $cidr) );
211 if ($recid) {
212 # Do the update if we've found an existing template with the same CIDR
213 $dbh->do("UPDATE records SET host = ?, type = ?, val = ? WHERE record_id = ?",
214 undef, ($patt, $newtype, $cidr, $recid) );
215 $template_modified = 1;
216 $tmpl_msg = ", replaced an existing template record";
217 } else {
218 $dbh->do("INSERT INTO records (domain_id, rdns_id, host, type, val, ttl, location) VALUES (?,?,?,?,?,?,?)",
219 undef, ($dparent, $zone, $patt, $newtype, $cidr, $soa->{minttl}, $ploc) );
220 $template_modified = 1;
221 }
222 } # $replace -> [id] or $replace == -1
223 } # $replace <> 'all'
224 } else {
225 # $replace == 0 (not set), just insert the new template
226 $dbh->do("INSERT INTO records (domain_id, rdns_id, host, type, val, ttl, location) VALUES (?,?,?,?,?,?,?)",
227 undef, ($dparent, $zone, $patt, $newtype, $cidr, $soa->{minttl}, $ploc) );
228 $template_modified = 1;
229 }
230
231 if ($template_modified) {
232 my %logdata = (rdns_id => $zone, domain_id => $dparent, group_id => 1,
233 entry => "A+PTR and/or PTR records in $cidr matching $patt replaced by $typemap{$newtype} record for $cidr");
234 $dnsdb->_updateserial(%logdata);
235 $dnsdb->_log(%logdata);
236 $dbh->commit;
237 } else {
238 # no need to do push out a null update that just bumps the serial on the zone(s)
239 $dbh->rollback;
240 }
241
242 };
243 if ($@) {
244 print "Error(s) encountered: $@\n";
245 $dbh->rollback;
246 return;
247 }
248 print " complete (removed $delcnt PTR/A+PTR records";
249 print $tmpl_msg;
250 print ")\n";
251} # squashem ()
Note: See TracBrowser for help on using the repository browser.