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