1 | #!/usr/bin/perl
|
---|
2 | # -T
|
---|
3 | # ipdb/cgi-bin/extras/db2rwhois.pl
|
---|
4 | # Pull data from ipdb and mangle it into RWHOIS
|
---|
5 | ###
|
---|
6 | # Revision info
|
---|
7 | # $Date: 2017-08-15 17:53:23 +0000 (Tue, 15 Aug 2017) $
|
---|
8 | # SVN revision $Rev: 906 $
|
---|
9 | # Last update by $Author: kdeugau $
|
---|
10 | ###
|
---|
11 | # Copyright (C) 2004-2010,2015,2016 - Kris Deugau
|
---|
12 |
|
---|
13 | use strict;
|
---|
14 | use warnings;
|
---|
15 | use DBI;
|
---|
16 | use NetAddr::IP;
|
---|
17 | use File::Path 'rmtree';
|
---|
18 | use POSIX qw(strftime);
|
---|
19 |
|
---|
20 | # don't remove! required for GNU/FHS-ish install from tarball
|
---|
21 | ##uselib##
|
---|
22 |
|
---|
23 | # push "the directory the script is in" into @INC
|
---|
24 | use FindBin;
|
---|
25 | use lib "$FindBin::RealBin/";
|
---|
26 |
|
---|
27 | use MyIPDB;
|
---|
28 |
|
---|
29 | #$ENV{"PATH"} = "/bin;/usr/bin";
|
---|
30 |
|
---|
31 | my @autharea;
|
---|
32 | my $authrw;
|
---|
33 | # Use the template file to allow us to keep persistent nodes aside from netblock data
|
---|
34 | open AUTHTEMPLATE, "<$IPDB::rwhoisDataPath/rwhoisd.auth_template";
|
---|
35 | my $template_persist;
|
---|
36 | while (<AUTHTEMPLATE>) {
|
---|
37 | next if /^##/;
|
---|
38 | $template_persist = 1 if /^[a-z]/i;
|
---|
39 | $autharea[0] .= $_;
|
---|
40 | }
|
---|
41 |
|
---|
42 | my ($dbh,$msg) = connectDB_My;
|
---|
43 |
|
---|
44 | # For WHOIS purposes this may not be very useful. YMMV, we'll see.
|
---|
45 | #initIPDBGlobals($dbh);
|
---|
46 |
|
---|
47 | my @masterblocks;
|
---|
48 | my %netnameprefix;
|
---|
49 |
|
---|
50 | # Get the list of live directories for potential deletion
|
---|
51 | opendir RWHOISROOT, $IPDB::rwhoisDataPath;
|
---|
52 | my %rwhoisdirs;
|
---|
53 | foreach (readdir RWHOISROOT) {
|
---|
54 | $rwhoisdirs{$_} = 1 if /^net-/;
|
---|
55 | }
|
---|
56 | closedir RWHOISROOT;
|
---|
57 |
|
---|
58 | # prefetch alloctype data
|
---|
59 | my $sth = $dbh->prepare("select type,def_custid,arin_netname from alloctypes");
|
---|
60 | $sth->execute;
|
---|
61 | while (my @data = $sth->fetchrow_array) {
|
---|
62 | $netnameprefix{$data[0]} = $data[2];
|
---|
63 | }
|
---|
64 |
|
---|
65 | # Get the list of masters to export
|
---|
66 | my $msth = $dbh->prepare(q(
|
---|
67 | SELECT cidr, createstamp, modifystamp, id
|
---|
68 | FROM allocations
|
---|
69 | WHERE type='mm' AND swip='y'
|
---|
70 | ) );
|
---|
71 | $msth->execute;
|
---|
72 |
|
---|
73 | # Prepare to select subblocks for each master
|
---|
74 | # Make sure to remove the private netblocks from this,
|
---|
75 | # no use or point in broadcasting our use of them.
|
---|
76 | # Also remove the details of our "reserved CORE/WAN" blocks; they're not critical.
|
---|
77 | my $ssth = $dbh->prepare(q(
|
---|
78 | SELECT a.cidr, a.custid, a.type, a.city, a.description, a.createstamp, a.modifystamp, a.swip, a.custid=t.def_custid AS isdef
|
---|
79 | FROM allocations a JOIN alloctypes t ON a.type=t.type
|
---|
80 | WHERE
|
---|
81 | NOT (a.cidr <<= '192.168.0.0/16') AND
|
---|
82 | NOT (a.cidr <<= '172.16.0.0/12') AND
|
---|
83 | NOT (a.cidr <<= '10.0.0.0/8') AND
|
---|
84 | NOT (a.type = 'wr' OR a.type LIKE '_m') AND
|
---|
85 | ((masklen(a.cidr) <=30 AND family(a.cidr)=4) OR (masklen(a.cidr) <=64 AND family(a.cidr)=6)) AND
|
---|
86 | a.master_id = ? AND
|
---|
87 | a.cidr <<= ?
|
---|
88 | ORDER BY a.cidr
|
---|
89 | ) );
|
---|
90 |
|
---|
91 | # Customer data, for those rare blocks we really need to delegate.
|
---|
92 | my $custsth = $dbh->prepare(q(
|
---|
93 | SELECT name, street, city, province, country, pocode, phone, tech_handle, special
|
---|
94 | FROM customers
|
---|
95 | WHERE custid = ?
|
---|
96 | ) );
|
---|
97 |
|
---|
98 | # Fill in data about our master blocks as allocated from ARIN
|
---|
99 | # We open separate files for each of these as appropriate.
|
---|
100 | # Changes in master blocks are treated as complete new masters - since we're exporting
|
---|
101 | # all data every time, this isn't so terrible as it might seem.
|
---|
102 | my $i=0;
|
---|
103 | while (my ($master, $mcreate, $mmod, $mid) = $msth->fetchrow_array()) {
|
---|
104 |
|
---|
105 | $masterblocks[$i] = new NetAddr::IP $master;
|
---|
106 | my ($ctime,undef) = split /\s/, $mcreate;
|
---|
107 | my ($mtime,undef) = split /\s/, $mmod;
|
---|
108 |
|
---|
109 | print "$masterblocks[$i] $ctime $mtime\n";
|
---|
110 |
|
---|
111 | my $date = strftime("%Y-%m-%d", localtime);
|
---|
112 |
|
---|
113 | my $rwnet = "net-".$masterblocks[$i]->addr."-".$masterblocks[$i]->masklen;
|
---|
114 |
|
---|
115 | # unflag the directory for deletion. Whee! Roundabout!
|
---|
116 | delete $rwhoisdirs{$rwnet};
|
---|
117 |
|
---|
118 | # Hokay. Gonna do checks *here* to see if we need to create new master trees
|
---|
119 | my $netdatadir = "$IPDB::rwhoisDataPath/$rwnet";
|
---|
120 | if (! -e $netdatadir) {
|
---|
121 | print " New master $masterblocks[$i]!\n";
|
---|
122 | print " Creating directories...\n";
|
---|
123 | mkdir $netdatadir;
|
---|
124 | mkdir "$netdatadir/attribute_defs";
|
---|
125 | mkdir "$netdatadir/data";
|
---|
126 | mkdir "$netdatadir/data/network";
|
---|
127 | mkdir "$netdatadir/data/org";
|
---|
128 | mkdir "$netdatadir/data/referral";
|
---|
129 |
|
---|
130 | my $serial = strftime("%Y%m%d%H%M%S000", localtime);
|
---|
131 |
|
---|
132 | ##fixme: SOA should be different every time data changes, therefore need to rewrite this ~~ every export :(
|
---|
133 | print " Creating SOA...\n";
|
---|
134 | open SOAFILE, ">$netdatadir/soa";
|
---|
135 | print SOAFILE qq(Serial-Number: $serial
|
---|
136 | Refresh-Interval: 3600
|
---|
137 | Increment-Interval: 1800
|
---|
138 | Retry-Interval: 1800
|
---|
139 | Time-To-Live: 86400
|
---|
140 | Primary-Server: rwhois.$IPDB::domain:4321
|
---|
141 | Hostmaster: $IPDB::hostmaster
|
---|
142 | );
|
---|
143 | close SOAFILE;
|
---|
144 |
|
---|
145 | print " Creating Schema...\n";
|
---|
146 | open SCHEMAFILE, ">$netdatadir/schema";
|
---|
147 | print SCHEMAFILE qq(name: network
|
---|
148 | attributedef: $rwnet/attribute_defs/network.tmpl
|
---|
149 | dbdir: $rwnet/data/network
|
---|
150 | Schema-Version: $serial
|
---|
151 | ---
|
---|
152 | name: organization
|
---|
153 | attributedef: $rwnet/attribute_defs/org.tmpl
|
---|
154 | dbdir: $rwnet/data/org
|
---|
155 | description: Organization object
|
---|
156 | Schema-Version: $serial
|
---|
157 | ---
|
---|
158 | name: referral
|
---|
159 | attributedef:$rwnet/attribute_defs/referral.tmpl
|
---|
160 | dbdir:$rwnet/data/referral
|
---|
161 | Schema-Version: $serial
|
---|
162 | );
|
---|
163 | close SCHEMAFILE;
|
---|
164 |
|
---|
165 | print " Copying template files...\n";
|
---|
166 | ##fixme: find a way to do this without a shell (or functional equivalent)
|
---|
167 | qx { /bin/cp $IPDB::rwhoisDataPath/skel/attribute_defs/* $netdatadir/attribute_defs/ };
|
---|
168 |
|
---|
169 | ##fixme: not sure if this is even necessary, since it's not referenced anywhere I can recall...
|
---|
170 | print " Creating org data...\n";
|
---|
171 | open ORGDATAFILE, ">$netdatadir/data/org/ourorg.txt";
|
---|
172 | print ORGDATAFILE qq(ID: NETBLK-$netnameprefix{mm}.$masterblocks[$i]
|
---|
173 | Auth-Area: $masterblocks[$i]
|
---|
174 | Org-Name: $IPDB::org_name
|
---|
175 | Street-Address: $IPDB::org_street
|
---|
176 | City: $IPDB::org_city
|
---|
177 | State: $IPDB::org_prov_state
|
---|
178 | Postal-Code: $IPDB::org_pocode
|
---|
179 | Country-Code: $IPDB::org_country
|
---|
180 | Phone: $IPDB::org_phone
|
---|
181 | Created: 20040308
|
---|
182 | Updated: 20040308
|
---|
183 | );
|
---|
184 | close ORGDATAFILE;
|
---|
185 |
|
---|
186 | # Generate auth_area record, and add it to the array.
|
---|
187 | $authrw = 1; # Flag for rewrite and daemon reload/restart
|
---|
188 |
|
---|
189 | } # new master
|
---|
190 |
|
---|
191 | # do this for all masters, so that we can use this array to export the data
|
---|
192 | # to rwhoisd.auth_area later if we need to
|
---|
193 | push @autharea, qq(type:master
|
---|
194 | name:$masterblocks[$i]
|
---|
195 | data-dir: $rwnet/data
|
---|
196 | schema-file: $rwnet/schema
|
---|
197 | soa-file: $rwnet/soa
|
---|
198 | );
|
---|
199 |
|
---|
200 | # Recreate the net-nnn.nnn.nnn.nnn-nn.txt data file
|
---|
201 | my $masterfilename = "$rwnet/data/network/".$masterblocks[$i]->addr."-".$masterblocks[$i]->masklen.".txt";
|
---|
202 |
|
---|
203 | open MASTERFILE,">$IPDB::rwhoisDataPath/$masterfilename";
|
---|
204 |
|
---|
205 | print MASTERFILE "ID: NETBLK-$netnameprefix{mm}.$masterblocks[$i]\n".
|
---|
206 | "Auth-Area: $masterblocks[$i]\n".
|
---|
207 | "Network-Name: $netnameprefix{mm}-".$masterblocks[$i]->network."\n".
|
---|
208 | "IP-Network: $masterblocks[$i]\n".
|
---|
209 | "IP-Network-Block: ".$masterblocks[$i]->range."\n".
|
---|
210 | "Org-Name: $IPDB::org_name\n".
|
---|
211 | "Street-Address: $IPDB::org_street\n".
|
---|
212 | "City: $IPDB::org_city\n".
|
---|
213 | "StateProv: $IPDB::org_prov_state\n".
|
---|
214 | "Postal-Code: $IPDB::org_pocode\n".
|
---|
215 | "Country-Code: $IPDB::org_country\n".
|
---|
216 | "Tech-Contact: $IPDB::org_techhandle\n".
|
---|
217 | "Created: $ctime\n".
|
---|
218 | "Updated: $mtime\n".
|
---|
219 | "Updated-By: $IPDB::org_email\n";
|
---|
220 |
|
---|
221 | # And now the subblocks
|
---|
222 | $ssth->execute($mid, $master) or die "nosubs: $!\n".$dbh->errstr."\n";
|
---|
223 | while (my ($cidr, $custid, $type, $city, $desc, $bctime, $bmtime, $swip, $defcust) = $ssth->fetchrow_array) {
|
---|
224 |
|
---|
225 | # We get master block info from @masterblocks.
|
---|
226 | # ID: NETBLK-$netnameprefix{mm}.10.0.0.0/8
|
---|
227 | # Auth-Area: 10.0.0.0/8
|
---|
228 | # Network-Name: $netnameprefix{$type}-10.0.2.144
|
---|
229 | # IP-Network: 10.0.2.144.144/29
|
---|
230 | # IP-Network-Block: 10.0.2.144 - 10.0.2.151
|
---|
231 | # Organization: WidgetCorp
|
---|
232 | # Tech-Contact: bob@widgetcorp.com
|
---|
233 | # Admin-Contact: ISP-ARIN-HANDLE
|
---|
234 | # Created: 20040314
|
---|
235 | # Updated: 20040314
|
---|
236 | # Updated-By: noc@example.com
|
---|
237 |
|
---|
238 | # Get the "full" network number
|
---|
239 | my $net = new NetAddr::IP $cidr;
|
---|
240 |
|
---|
241 | # Assumptions: All data in ipdb is public
|
---|
242 | # If not, we need another field to indicate "public/private".
|
---|
243 |
|
---|
244 | # cidr custid type city description notes maskbits
|
---|
245 |
|
---|
246 | # Fill in a generic entry for nameless allocations
|
---|
247 | if ($desc =~ /^\s*$/) { $desc = $IPDB::org_name; }
|
---|
248 |
|
---|
249 | # Fix up datestamps. We don't *really* need sub-microsecond resolution on our exports...
|
---|
250 | ($bctime) = ($bctime =~ /^(\d+-\d+-\d+)\s+/);
|
---|
251 | ($bmtime) = ($bmtime =~ /^(\d+-\d+-\d+)\s+/);
|
---|
252 |
|
---|
253 | # Notes:
|
---|
254 | # Network-name should contain some component of "description"
|
---|
255 | # Cust address/contact data should be included; NB, no phone for ARIN!
|
---|
256 | # network:ID: NET-WIDGET
|
---|
257 | # network:Network-Name: WIDGET [IPDB description, sort of]
|
---|
258 | # network:IP-Network: 10.1.1.0/24
|
---|
259 | # network:Org-Name: Widget Corp [Cust name; from billing?]
|
---|
260 | # network:Street-Address: 211 Oak Drive [May need more than one line, OR...]
|
---|
261 | # network:City: Pineville [...this line...]
|
---|
262 | # network:StateProv: WI [...and this line...]
|
---|
263 | # network:Postal-Code: 48888 [...and this line]
|
---|
264 | # network:Country-Code: US
|
---|
265 | # network:Tech-Contact: BZ142-MYRWHOIS [ARIN handle?]
|
---|
266 | # network:Updated: 19991221 [timestamp from db]
|
---|
267 | # network:Updated-By: jo@myrwhois.net [noc@example, since that's our POC for IP netspace issues]
|
---|
268 | # network:Class-Name:network [Provided by rWHOIS protocol]
|
---|
269 |
|
---|
270 | my $netname = $netnameprefix{$type};
|
---|
271 |
|
---|
272 | if ($swip eq 'n' || $defcust) {
|
---|
273 | print MASTERFILE "---\nID: NETBLK-$netnameprefix{mm}.$masterblocks[$i]\n".
|
---|
274 | "Auth-Area: $masterblocks[$i]\n".
|
---|
275 | "Network-Name: $netname-".$net->network."\n".
|
---|
276 | "IP-Network: $net\n".
|
---|
277 | "IP-Network-Block: ".$net->range."\n".
|
---|
278 | "Org-Name: $IPDB::org_name\n".
|
---|
279 | "Street-Address: $IPDB::org_street\n".
|
---|
280 | "City: $IPDB::org_city\n".
|
---|
281 | "StateProv: $IPDB::org_prov_state\n".
|
---|
282 | "Postal-Code: $IPDB::org_pocode\n".
|
---|
283 | "Country-Code: $IPDB::org_country\n".
|
---|
284 | "Tech-Contact: $IPDB::org_techhandle\n".
|
---|
285 | "Created: $bctime\n".
|
---|
286 | "Updated: $bmtime\n".
|
---|
287 | "Updated-By: $IPDB::org_email\n";
|
---|
288 | } else {
|
---|
289 | $custsth->execute($custid);
|
---|
290 | my ($name, $street, $city, $prov, $country, $pocode, $phone, $tech, $special) = $custsth->fetchrow_array;
|
---|
291 | $custsth->finish;
|
---|
292 | if ($special && $special =~ /NetName/ && $special =~ /$cidr/) {
|
---|
293 | ($netname) = ($special =~ /NetName$cidr: ([A-Z0-9_-]+)/);
|
---|
294 | } else {
|
---|
295 | $netname .= "-".$net->network;
|
---|
296 | }
|
---|
297 | print MASTERFILE "---\nID: NETBLK-$netnameprefix{mm}.$masterblocks[$i]\n".
|
---|
298 | "Auth-Area: $masterblocks[$i]\n".
|
---|
299 | "Network-Name: $netname\n".
|
---|
300 | "IP-Network: $net\n".
|
---|
301 | "IP-Network-Block: ".$net->range."\n".
|
---|
302 | "Org-Name: ".($name ? $name : $IPDB::org_name)."\n".
|
---|
303 | "Street-Address: ".($street ? $street : $IPDB::org_street)."\n".
|
---|
304 | "City: ".($city ? $city : $IPDB::org_city)."\n".
|
---|
305 | "StateProv: ".($prov ? $prov : $IPDB::org_prov_state)."\n".
|
---|
306 | "Postal-Code: ".($pocode ? $pocode : $IPDB::org_pocode)."\n".
|
---|
307 | "Country-Code: ".($country ? $country : $IPDB::org_country)."\n".
|
---|
308 | "Tech-Contact: ".($tech ? $tech : $IPDB::org_techhandle)."\n".
|
---|
309 | "Created: $bctime\n".
|
---|
310 | "Updated: $bmtime\n".
|
---|
311 | "Updated-By: $IPDB::org_email\n";
|
---|
312 | } # swip
|
---|
313 |
|
---|
314 | } # while $ssth->fetchrow_array()
|
---|
315 |
|
---|
316 | close MASTERFILE;
|
---|
317 |
|
---|
318 | $i++;
|
---|
319 | } # while $msth->fetchrow_array()
|
---|
320 |
|
---|
321 | # Now we see if there's obsolete netdata directories to be deleted,
|
---|
322 | # and therefore an auth-area file to regenerate
|
---|
323 | foreach my $netdir (keys %rwhoisdirs) {
|
---|
324 | print "deleting obsolete directory $netdir...\n";
|
---|
325 | rmtree ( "$IPDB::rwhoisDataPath/$netdir", { verbose => 1, error => \my $errlist } );
|
---|
326 | for my $diag (@$errlist) {
|
---|
327 | my ($file, $message) = each %$diag;
|
---|
328 | if ($file eq '') {
|
---|
329 | print "general error: $message\n";
|
---|
330 | }
|
---|
331 | }
|
---|
332 | $authrw = 1; # there's probably a more efficient place to put this. Feh.
|
---|
333 | }
|
---|
334 |
|
---|
335 | # Regenerate rwhoisd.auth_area if needed
|
---|
336 | if ($authrw) {
|
---|
337 | print "Regenerating auth_area\n";
|
---|
338 | open RWHOISDAUTH, ">$IPDB::rwhoisDataPath/rwhoisd.auth_area";
|
---|
339 | print RWHOISDAUTH "# WARNING: This file is autogenerated! Any static nodes should\n".
|
---|
340 | "# be entered in /etc/rwhoisd/rwhoisd.auth_template\n";
|
---|
341 | if ($template_persist) {
|
---|
342 | print RWHOISDAUTH shift @autharea;
|
---|
343 | print RWHOISDAUTH "---\n";
|
---|
344 | }
|
---|
345 | # feh. we need to know when we're at the end of the loop, because then
|
---|
346 | # we DON'T want to write the separator...
|
---|
347 | for (;@autharea;) { # my head hurts.
|
---|
348 | print RWHOISDAUTH shift @autharea;
|
---|
349 | print RWHOISDAUTH "---\n" if @autharea;
|
---|
350 | }
|
---|
351 | close RWHOISDAUTH;
|
---|
352 |
|
---|
353 | # restart/reload rwhoisd
|
---|
354 | if (-e "$IPDB::rwhoisDataPath/rwhoisd.pid") { # no pidfile, no restart.
|
---|
355 | print "Restarting rwhoisd\n";
|
---|
356 | open PIDFILE, "<$IPDB::rwhoisDataPath/rwhoisd.pid";
|
---|
357 | my ($rwpid) = (<PIDFILE> =~ /^(\d+)/);
|
---|
358 | close PIDFILE;
|
---|
359 | kill 'HUP', $rwpid;
|
---|
360 | }
|
---|
361 | }
|
---|
362 |
|
---|
363 | # and finally
|
---|
364 | $dbh->disconnect;
|
---|