File Coverage

blib/lib/Net/XWhois.pm
Criterion Covered Total %
statement 12 170 7.0
branch 0 92 0.0
condition 0 45 0.0
subroutine 4 17 23.5
pod 6 10 60.0
total 22 334 6.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             ##
3             ## Net::XWhois
4             ## Whois Client Interface Class.
5             ##
6             ## $Date: 2001/07/14 07:25:31 $
7             ## $Revision: 1.3 $
8             ## $State: Exp $
9             ## $Author: vipul $
10             ##
11             ## Copyright (c) 1998, Vipul Ved Prakash. All rights reserved.
12             ## This code is free software; you can redistribute it and/or modify
13             ## it under the same terms as Perl itself.
14             #
15             # modified August 2002 by Rob Woodard
16             #
17             # Changes:
18             #
19             # 08/05/2002 rwoodard Merged in changes from XWhois discussion forum on
20             # sourceforge.net; made additional changes as needed
21             # to implement reverse lookups of IP addresses
22             # 08/06/2002 rwoodard Added comments for internal documentation. Added
23             # parser defs for ARIN, set APNIC and RIPE to use RPSL.
24             # 08/07/2002 rwoodard Added ARIN-specific following of multiple netblocks;
25             # this is done by setting the Bottom_netblock attrib
26             # 08/08/2002 rwoodard Added Verbose attribute for displaying status info
27             # 08/26/2002 rwoodard Revised ARIN parser to reflect updated responses
28             #
29              
30             package Net::XWhois;
31              
32 1     1   3277 use Data::Dumper;
  1         19091  
  1         95  
33 1     1   1696 use IO::Socket;
  1         54202  
  1         6  
34 1     1   1092 use Carp;
  1         7  
  1         62  
35 1     1   6 use vars qw ( $VERSION $AUTOLOAD );
  1         1  
  1         4722  
36              
37             $VERSION = '0.90';
38              
39             my $CACHE = "/tmp/whois";
40             my $EXPIRE = 604800;
41             my $ERROR = "return";
42             my $TIMEOUT = 20;
43             my $RETRIES = 3;
44              
45             my %PARSERS = (
46              
47             #these are the parser definitions for each whois server.
48             #the AUTOLOAD subroutine creates an object method for each key defined within
49             #the server's hash of regexps; this applies the regexp to the response from
50             #the whois server to extract the data. of course you can just write your own
51             #parsing subroutine as described in the docs.
52             #
53             #there ought to be some standardization of the fields being parsed. for my
54             #own personal purposes only RPSL and ARIN are standardized; there needs to be
55             #some work done on the other defs to get them to return at least these fields:
56             #
57             # name name of registrant entity (company or person)
58             # netname name assigned to registrant's network
59             # inetnum address range registered
60             # abuse_email email addresses named 'abuse@yaddayadda'
61             # gen_email general correspondence email addresses
62             #
63             #yes some of these are redundant to what is already there; I saw no reason to
64             #delete non-standardized keys, they don't take that much space and might be
65             #needed for backwards compatibility. -rwoodard 08/2002
66            
67             RPSL => { #updated by rwoodard 08/06/2002
68             name => '(?:descr|owner):\s+([^\n]*)\n',
69             netname => 'netname:\s+([^\n]*)\n',
70             inetnum => 'inetnum:\s+([^\n]*)\n',
71             abuse_email => '\b(?:abuse|security)\@\S+',
72             gen_email => 'e-*mail:\s+(\S+\@\S+)',
73            
74             country => 'country:\s+(\S+)',
75             status => 'status:\s+([^\n]*)\n',
76             contact_admin => '(?:admin|owner)-c:\s+([^\n]*)\n',
77             contact_tech => 'tech-c:\s+([^\n]*)\n',
78             contact_emails => 'email:\s+(\S+\@\S+)',
79             contact_handles => 'nic-hdl(?:-\S*):\s+([^\n]*)\n',
80             remarks => 'remarks:\s+([^\n]*)\n',
81             notify => 'notify:\s+([^\n]*)\n',
82             forwardwhois => 'remarks:\s+[^\n]*(whois.\w+.\w+)',
83             },
84              
85             ARIN => { #from Jon Gilbert 09/04/2000 updated/added to by rwoodard 08/07/2002
86            
87             name => '(?:OrgName|CustName):\s*(.*?)\n',
88            
89             netname => 'etName:\s*(\S+)\n+',
90             inetnum => 'etRange:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} - \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[\n\s]*',
91             abuse_email => '(?:abuse|security)\@\S+',
92             gen_email => 'Coordinator:[\n\s]+.*?(\S+\@\S+)',
93              
94             netnum => 'Netnumber:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[\n\s]*',
95             hostname => 'Hostname:\s*(\S+)[\n\s]*',
96             maintainer => 'Maintainer:\s*(\S+)',
97             #record_update => 'Record last updated on (\S+)\.\n+',
98             record_update => 'Updated:(\S+)\n+',
99             database_update => 'Database last updated on (.+)\.[\n\s]+The',
100             registrant => '^(.*?)\n\n',
101             reverse_mapping => 'Domain System inverse[\s\w]+:[\n\s]+(.*?)\n\n',
102             coordinator => 'Coordinator:[\n\s]+(.*?)\n\n',
103             coordinator_handle => 'Coordinator:[\n\s]+[^\(\)]+\((\S+?)\)',
104             coordinator_email => 'Coordinator:[\n\s]+.*?(\S+\@\S+)',
105             address => 'Address:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',
106             system => 'System:\s+([^\n]*)\n',
107             non_portable => 'ADDRESSES WITHIN THIS BLOCK ARE NON-PORTABLE',
108             #multiple => 'To single out one record',
109             multiple => '\((NET\S+)\)',
110             net_handle => '(NET\S+)\)',
111             country => 'Country:\s*(\S+)\n+',
112             },
113            
114             BRNIC => {
115             name => '(?:descr|owner):\s+([^\n]*)\n',
116             netname => 'netname:\s+([^\n]*)\n',
117             inetnum => 'inetnum:\s+([^\n]*)\n',
118             abuse_email => '\b(?:abuse|security)\@\S+',
119             gen_email => 'e-*mail:\s+(\S+\@\S+)',
120            
121             country => 'BR', #yes this is ugly, tell BRNIC to start putting country fields in their responses
122             status => 'status:\s+([^\n]*)\n',
123             contact_admin => '(?:admin|owner)-c:\s+([^\n]*)\n',
124             contact_tech => 'tech-c:\s+([^\n]*)\n',
125             contact_emails => 'email:\s+(\S+\@\S+)',
126             contact_handles => 'nic-hdl(?:-\S*):\s+([^\n]*)\n',
127             remarks => 'remarks:\s+([^\n]*)\n',
128             notify => 'notify:\s+([^\n]*)\n',
129             forwardwhois => 'remarks:\s+[^\n]*(whois.\w+.\w+)',
130             },
131            
132             KRNIC => { #added by rwoodard 08/06/2002
133              
134             },
135              
136             TWNIC => { #added by rwoodard 08/06/2002
137             name => '^([^\n]*)\n',
138             netname => 'etname:\s*(\S+)\n+',
139             inetnum => 'etblock:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} - \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[\n\s]*',
140             abuse_email => '(?:abuse|security)\@\S+',
141             gen_email => 'Coordinator:[\n\s]+.*?(\S+\@\S+)',
142              
143             netnum => 'Netnumber:\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[\n\s]*',
144             hostname => 'Hostname:\s*(\S+)[\n\s]*',
145             maintainer => 'Maintainer:\s*(\S+)',
146             record_update => 'Record last updated on (\S+)\.\n+',
147             database_update => 'Database last updated on (.+)\.[\n\s]+The',
148             registrant => '^(.*?)\n\n',
149             reverse_mapping => 'Domain System inverse[\s\w]+:[\n\s]+(.*?)\n\n',
150             coordinator => 'Coordinator:[\n\s]+(.*?)\n\n',
151             coordinator_handle => 'Coordinator:[\n\s]+[^\(\)]+\((\S+?)\)',
152             coordinator_email => 'Coordinator:[\n\s]+.*?(\S+\@\S+)',
153             address => 'Address:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',
154             system => 'System:\s+([^\n]*)\n',
155             non_portable => 'ADDRESSES WITHIN THIS BLOCK ARE NON-PORTABLE',
156             multiple => 'To single out one record',
157             net_handle => '\((NETBLK\S+)\)',
158             country => '\n\s+(\S+)\n\n',
159             },
160            
161             INTERNIC => {
162             name => '[\n\r\f]+\s*[Dd]omain [Nn]ame[:\.]*\s+(\S+)',
163             status => 'omain Status[:\.]+\s+(.*?)\s*\n',
164             nameservers => '[\n\r\f]+\s*([a-zA-Z0-9\-\.]+\.[a-zA-Z0-9\-]+\.[a-zA-Z\-]+)[:\s\n$]',
165             registrant => '(?:egistrant|rgani[sz]ation)[:\.]*\s*\n(.*?)\n\n',
166             contact_admin => '(?:dministrative Contact|dmin Contact).*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
167             contact_tech => '(?:echnical Contact|ech Contact).*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
168             contact_zone => 'one Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
169             contact_billing => 'illing Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
170             contact_emails => '(\S+\@\S+)',
171             contact_handles => '\(([^\W\d]+\d+)\)',
172             domain_handles => '\((\S*?-DOM)\)',
173             org_handles => '\((\S*?-ORG)\)',
174             not_registered => 'No match',
175             forwardwhois => 'Whois Server: (.*?)(?=\n)',
176             },
177              
178             BULKREG => {
179             name => 'omain Name[:\.]*\s+(\S+)',
180             status => 'omain Status[:\.]+\s+(.*?)\s*\n',
181             nameservers => '[\n\r\f]+\s*([a-zA-Z0-9\-\.]+\.[a-zA-Z0-9\-]+\.[a-zA-Z\-]+)[:\s\n$]',
182             registrant => '(.+)\([\w\-]+\-DOM\).*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
183             contact_admin => 'dmin[a-zA-Z]*? Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
184             contact_tech => 'ech[a-zA-Z]*? Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
185             contact_zone => 'one Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
186             contact_billing => 'illing Contact.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
187             contact_emails => '(\S+\@\S+)',
188             contact_handles => '\((\w+\d+\-BR)\)',
189             domain_handles => '\((\S*?-DOM)\)',
190             org_handles => '\((\S*?-ORG)\)',
191             not_registered => 'Not found\!',
192             forwardwhois => 'Whois Server: (.*?)(?=\n)',
193             registrar => 'egistrar\s*\w*[\.\:]* (.*?)\.?\n',
194             reg_date => 'reated on[\.\:]* (.*?)\.?\n',
195             exp_date => 'xpires on[\.\:]* (.*?)\.?\n',
196             },
197              
198             INWW => {
199             name => 'omain Name\.+ (\S+)',
200             status => 'omain Status\.+ ([^\n]*)\n',
201             nameservers => 'Name Server\.+ (\S+)',
202             registrant => 'Organisation \w{4,7}\.+ ([^\n]+?)\n',
203             contact_admin => 'Admin \w{3,7}\.+ ([^\n]*)\n',
204             contact_tech => 'Tech \w{3,7}\.+ ([^\n]*)\n',
205             contact_zone => 'Zone \w{3,7}\.+ ([^\n]*)\n',
206             contact_billing => 'Billing \w{3,7}\.+ ([^\n]*)\n',
207             contact_emails => '(\S+\@\S+)',
208             contact_handles => '\((\w+\d+)\)',
209             domain_handles => '\((\S*?-DOM)\)',
210             org_handles => '\((\S*?-ORG)\)',
211             not_registered => 'is not registered',
212             forwardwhois => 'Whois Server: (.*?)(?=\n)',
213             registrar => 'egistrar\s*\w*[\.\:]* (.*?)\.?\n',
214             exp_date => 'Expiry Date\.+ ([^\n]*)\n',
215             reg_date => 'Registration Date\.+ ([^\n]*)\n',
216             },
217              
218             INTERNIC_CONTACT => {
219             name => '(.+?)\s+\(.*?\)(?:.*?\@)',
220             address => '\n(.*?)\n[^\n]*?\n\n\s+Re',
221             email => '\s+\(.*?\)\s+(\S+\@\S+)',
222             phone => '\n([^\n]*?)\(F[^\n]+\n\n\s+Re',
223             fax => '\(FAX\)\s+([^\n]+)\n\n\s+Re',
224             },
225              
226             CANADA => {
227             name => 'domain:\s+(\S+)\n',
228             nameservers => '-Netaddress:\s+(\S+)',
229             contact_emails => '-Mailbox:\s+(\S+\@\S+)',
230             },
231              
232             RIPE => {
233             name => 'domain:\s+(\S+)\n',
234             nameservers => 'nserver:\s+(\S+)',
235             contact_emails => 'e-mail:\s+(\S+\@\S+)',
236             registrant => 'descr:\s+(.+?)\n',
237             },
238              
239             RIPE_CH => {
240             name => 'Domain Name:[\s\n]+(\S+)\n',
241             nameservers => 'Name servers:[\s\n]+(\S+)[\s\n]+(\S+)',
242             },
243              
244             NOMINET => {
245             name => 'omain Name:\s+(\S+)',
246             registrant => 'egistered For:\s*(.*?)\n',
247             ips_tag => 'omain Registered By:\s*(.*?)\n',
248             record_updated_date => 'Record last updated on\s*(.*?)\s+',
249             record_updated_by => 'Record last updated on\s*.*?\s+by\s+(.*?)\n',
250             nameservers => 'listed in order:[\s\n]+(\S+)\s.*?\n\s+(\S*?)\s.*?\n\s*\n',
251             whois_updated => 'database last updated at\s*(.*?)\n',
252             },
253              
254             UKERNA => {
255             name => 'omain Name:\s+(\S+)',
256             registrant => 'egistered For:\s*(.*?)\n',
257             ips_tag => 'omain Registered By:\s*(.*?)\n',
258             record_updated_date => 'ecord updated on\s*(.*?)\s+',
259             record_updated_by => 'ecord updated on\s*.*?\s+by\s+(.*?)\n',
260             nameservers => 'elegated Name Servers:[\s\n]+(\S+)[\s\n]+(\S+).*?\n\s*\n',
261             contact_emails => 'Domain contact:\s*(.*?)\n',
262             },
263              
264             CENTRALNIC => {
265             name => 'omain Name:\s+(\S+)',
266             registrant => 'egistrant:\s*(.*?)\n',
267             contact_admin => 'lient Contact:\s*(.*?)\n\s*\n',
268             contact_billing => 'illing Contact:\s*(.*?)\n\s*\n',
269             contact_tech => 'echnical Contact:\s*(.*?)\n\s*\n',
270             record_created_date => 'ecord created on\s*(.*?)\n',
271             record_paid_date => 'ecord paid up to\s*(.*?)\n',
272             record_updated_date => 'ecord last updated on\s*(.*?)\n',
273             nameservers => 'in listed order:[\s\n]+(\S+)\s.*?\n\s+(\S*?)\s.*?\n\s*\n',
274             contact_emails => '(\S+\@\S+)',
275             },
276              
277             DENIC => {
278             name => 'domain:\s+(\S+)\n',
279             registrants => 'descr:\s+(.+?)\n',
280             contact_admin => 'admin-c:\s+(.*?)\s*\n',
281             contact_tech => 'tech-c:\s+(.*?)\s*\n',
282             contact_zone => 'zone-c:\s+(.*?)\s*\n',
283             nameservers => 'nserver:\s+(\S+)',
284             status => 'status:\s+(.*?)\s*\n',
285             changed => 'changed:\s+(.*?)\s*\n',
286             source => 'source:\s+(.*?)\s*\n',
287             person => 'person:\s+(.*?)\s*\n',
288             address => 'address:\s+(.+?)\n',
289             phone => 'phone:\s+(.+?)\n',
290             fax_no => 'fax-no:\s+(.+?)\n',
291             contact_emails => 'e-mail:\s+(.+?)\n',
292             },
293              
294             JAPAN => {
295             name => '\[Domain Name\]\s+(\S+)',
296             nameservers => 'Name Server\]\s+(\S+)',
297             contact_emails => '\[Reply Mail\]\s+(\S+\@\S+)',
298             },
299              
300             TAIWAN => {
301             name => 'omain Name:\s+(\S+)',
302             registrant => '^(\S+) \(\S+?DOM)',
303             contact_emails => '(\S+\@\S+)',
304             nameservers => 'servers in listed order:[\s\n]+\%see\-also\s+\.(\S+?)\:',
305             },
306              
307             KOREA => {
308             name => 'Domain Name\s+:\s+(\S+)',
309             nameservers => 'Host Name\s+:\s+(\S+)',
310             contact_emails => 'E\-Mail\s+:\s*(\S+\@\S+)',
311             },
312              
313             MEXICO => {
314             name => '[\n\r\f]+\s*[Nn]ombre del [Dd]ominio[:\.]*\s+(\S+)',
315             status => 'omain Status[:\.]+\s+(.*?)\s*\n',
316             nameservers => 'ameserver[^:]*:\s*([a-zA-Z0-9.\-])+',
317             registrant => '(?:egistrant|rgani[sz]acion)[:\.]*\s*\n(.*?)\n\n',
318             contact_admin => '(?:tacto [Aa]dministrativo|dmin Contact).*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
319             contact_tech => '(?:tacto [Tt]ecnico|ech Contact).*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
320             contact_billing => 'to de Pago.*?\n(.*?)(?=\s*\n[^\n]+?:\s*\n|[\n\r\f]{2})',
321             contact_emails => '(\S+\@\S+)',
322             contact_handles => '\(([^\W\d]+\d+)\)',
323             not_registered => 'No Encontrado',
324             reg_date => 'de creacion[\.\:]* (.*?)\.?\n',
325             record_updated_date => 'a modificacion[\.\:]* (.*?)\.?\n',
326             },
327              
328             ADAMS => {
329             name => '(\S+) is \S*\s*registered',
330             not_registered => 'is not registered',
331             },
332              
333              
334              
335             GENERIC => {
336             contact_emails => '(\S+\@\S+)',
337             },
338            
339             );
340              
341             my %WHOIS_PARSER = (
342             'whois.ripe.net' => 'RPSL',
343             'whois.nic.mil' => 'INTERNIC',
344             'whois.nic.ad.jp' => 'JAPAN',
345             'whois.domainz.net.nz' => 'GENERIC',
346             'whois.nic.gov' => 'INTERNIC',
347             'whois.nic.ch' => 'RIPE_CH',
348             'whois.twnic.net' => 'TWNIC',
349             'whois.internic.net' => 'INTERNIC',
350             'whois.aunic.net' => 'RIPE',
351             'whois.cdnnet.ca' => 'CANADA',
352             'whois.ja.net' => 'UKERNA',
353             'whois.nic.uk' => 'NOMINET',
354             'whois.krnic.net' => 'KOREA',
355             'whois.isi.edu' => 'INTERNIC',
356             'whois.norid.no' => 'RPSL',
357             'whois.centralnic.com' => 'CENTRALNIC',
358             'whois.denic.de' => 'DENIC',
359             'whois.InternetNamesWW.com' => 'INWW',
360             'whois.bulkregister.com' => 'BULKREG',
361             'whois.arin.net' => 'ARIN', #added 08/06/2002 by rwoodard
362             'whois.apnic.net' => 'RPSL', #added 08/06/2002 by rwoodard
363             'whois.nic.fr' => 'RPSL',
364             'whois.lacnic.net' => 'RPSL',
365             'whois.nic.br' => 'BRNIC',
366             'whois.nic.mx' => 'MEXICO',
367             'whois.adamsnames.tc' => 'ADAMS',
368             );
369              
370             my %DOMAIN_ASSOC = (
371              
372             'al' => 'whois.ripe.net', 'am' => 'whois.ripe.net',
373             'at' => 'whois.ripe.net', 'au' => 'whois.aunic.net',
374             'az' => 'whois.ripe.net',
375             'ba' => 'whois.ripe.net', 'be' => 'whois.ripe.net',
376             'bg' => 'whois.ripe.net', 'by' => 'whois.ripe.net',
377             'ca' => 'whois.cdnnet.ca', 'ch' => 'whois.nic.ch',
378             'com' => 'whois.internic.net',
379             'cy' => 'whois.ripe.net', 'cz' => 'whois.ripe.net',
380             'de' => 'whois.denic.de', 'dk' => 'whois.dk-hostmaster.dk',
381             'dz' => 'whois.ripe.net',
382             'edu' => 'whois.internic.net', 'ee' => 'whois.ripe.net',
383             'eg' => 'whois.ripe.net', 'es' => 'whois.ripe.net',
384             'fi' => 'whois.ripe.net', 'fo' => 'whois.ripe.net',
385             'fr' => 'whois.nic.fr',
386             'gb' => 'whois.ripe.net', 'ge' => 'whois.ripe.net',
387             'gov' => 'whois.nic.gov', 'gr' => 'whois.ripe.net',
388             'hr' => 'whois.ripe.net', 'hu' => 'whois.ripe.net',
389             'ie' => 'whois.ripe.net', 'il' => 'whois.ripe.net',
390             'is' => 'whois.ripe.net', 'it' => 'whois.ripe.net',
391             'jp' => 'whois.nic.ad.jp',
392             'kr' => 'whois.krnic.net',
393             'li' => 'whois.ripe.net', 'lt' => 'whois.ripe.net',
394             'lu' => 'whois.ripe.net', 'lv' => 'whois.ripe.net',
395             'ma' => 'whois.ripe.net', 'md' => 'whois.ripe.net',
396             'mil' => 'whois.nic.mil', 'mk' => 'whois.ripe.net',
397             'mt' => 'whois.ripe.net', 'mx' => 'whois.nic.mx',
398             'net' => 'whois.internic.net', 'nl' => 'whois.ripe.net',
399             'no' => 'whois.norid.no', 'nz' => 'whois.domainz.net.nz',
400             'org' => 'whois.internic.net',
401             'pl' => 'whois.ripe.net', 'pt' => 'whois.ripe.net',
402             'ro' => 'whois.ripe.net', 'ru' => 'whois.ripe.net',
403             'se' => 'whois.ripe.net', 'sg' => 'whois.nic.net.sg',
404             'si' => 'whois.ripe.net', 'sk' => 'whois.ripe.net',
405             'sm' => 'whois.ripe.net', 'su' => 'whois.ripe.net',
406             'tn' => 'whois.ripe.net', 'tr' => 'whois.ripe.net',
407             'tw' => 'whois.twnic.net',
408             'ua' => 'whois.ripe.net',
409              
410             'uk' => 'whois.nic.uk',
411             'gov.uk' => 'whois.ja.net',
412             'ac.uk' => 'whois.ja.net',
413             'eu.com' => 'whois.centralnic.com',
414             'uk.com' => 'whois.centralnic.com',
415             'uk.net' => 'whois.centralnic.com',
416             'gb.com' => 'whois.centralnic.com',
417             'gb.net' => 'whois.centralnic.com',
418              
419             'us' => 'whois.isi.edu',
420             'va' => 'whois.ripe.net',
421             'yu' => 'whois.ripe.net',
422            
423             );
424              
425             my %ARGS = (
426             'whois.nic.ad.jp' => { 'S' => '/e' },
427             'whois.internic.net' => { 'P' => '=' },
428             'whois.networksolutions.com' => { 'P' => '=' },
429             );
430              
431             sub register_parser {
432              
433 0     0 1   my ( $self, %args ) = @_;
434              
435 0 0         $self->{ _PARSERS }->{ $args{ Name } } = {} unless $args{ Retain }; #set Retain to keep parser entries already present
436 0           for ( keys %{ $args{ Parser } } ) {
  0            
437 0           $self->{ _PARSERS }->{ $args{ Name } }->{$_} = $args{ Parser }->{$_};
438             }
439              
440 0           return 1;
441              
442             }
443              
444             sub register_association {
445              
446 0     0 1   my ( $self, %args ) = @_;
447 0           foreach my $server ( keys %args ) {
448             # Update our table for looking up the whois server => parser
449 0           $self->{ _WHOIS_PARSER }->{ $server } = $args{ $server }->[0]; # Save name of whois server and associated parser
450             # Update our table of domains and their associated server
451             #$self->{ _DOMAIN_ASSOC }->{ $_ } = $server for ( @{$args{ $server }}->[1]);
452 0           $self->{ _DOMAIN_ASSOC }->{ $_ } = $server for ( @{$args{ $server }->[1]}); #from Paul Fuchs
  0            
453             };
454              
455 0           return 1;
456              
457             }
458              
459             sub register_cache {
460              
461 0     0 1   my ( $self, $cache ) = @_;
462 0 0         return ${ $self->{ _CACHE } } = $cache if $cache;
  0            
463              
464             }
465              
466             sub server {
467 0     0 0   my $self = shift;
468 0           return $self->{ Server };
469              
470             }
471              
472             sub guess_server_details {
473              
474 0     0 0   my ( $self, $domain ) = @_;
475 0           $domain = lc $domain;
476              
477 0           my $ip=$domain=~/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/; #processing an IP?
478 0           my ( $server, $parser );
479             my ( $Dserver, $Dparser ) =
480 0           $ip ? ( 'whois.arin.net', { %{ $self->{ _PARSERS }->{ ARIN } } }) :
481 0 0         ( 'whois.internic.net', { %{ $self->{ _PARSERS }->{ INTERNIC } } } ) ;
  0            
482            
483             #figure out what our server and parser should be
484 0 0         if ($ip) {
485 0 0         $server= $self->{ Server } ? $self->{ Server } : 'whois.arin.net' ;
486             }
487             else {
488 0           $domain =~ s/.*\.(\w+\.\w+)$/$1/; #peels off the last two elements
489 0           $server = $self->{ _DOMAIN_ASSOC }->{ $domain };
490              
491 0 0         unless ($server) {
492 0           $domain =~ s/.*\.(\w+)$/$1/; #peels off the last element
493 0           $server = $self->{ _DOMAIN_ASSOC }->{ $domain };
494             }
495             }
496 0 0         $parser = $self->{ _PARSERS }->{ $self->{ _WHOIS_PARSER }->{ $server } } if ($server);
497             #print "domain $domain server $server parser $parser\n";
498 0 0         return $server ? [$server, $parser] : [$Dserver, $Dparser];
499             };
500              
501             sub new {
502 0     0 1   my ( $class, %args ) = @_;
503              
504 0           my $self = {};
505 0           $self->{ _PARSERS } = \%PARSERS;
506 0           $self->{ _DOMAIN_ASSOC } = \%DOMAIN_ASSOC;
507 0           $self->{ _WHOIS_PARSER } = \%WHOIS_PARSER;
508 0   0       $self->{ _CACHE } = $args{Cache} || \$CACHE;
509 0   0       $self->{ _EXPIRE } = $args{Expire} || \$EXPIRE;
510 0           $self->{ _ARGS } = \%ARGS;
511              
512 0           bless $self, $class;
513              
514 0           $self->personality ( %args );
515 0 0         $self->lookup () if $self->{ Domain };
516 0           return $self;
517              
518             }
519              
520             sub personality {
521 0     0 1   my ( $self, %args ) = @_;
522              
523             #set all attributes that were passed in
524 0 0         for ( keys %args ) {chomp $args{ $_} if defined($args{ $_}); $self->{ $_ }=$args{ $_ } }
  0            
  0            
525             $self->{ Parser } = $self->{ _PARSERS }->{ $args{ Format } }
526 0 0         if $args{ Format }; #lets you pick an alternate parser set
527              
528             #if we don't have a whois server to use, guess based on the Domain (or IP)
529 0 0         unless ( $self->{ Server } ) {
530 0           my $res = $self->guess_server_details ( $self->{ Domain } );
531 0           ( $self->{ Server }, undef ) = @$res;
532             }
533              
534             #if there is already a Parser defined for this server, use it
535 0 0         if ( $self->{ _PARSERS }->{ $self->{ Server }}) {
536 0           $self->{ Parser } = $self->{ _PARSERS }->{ $self->{ Server }};
537             }
538              
539             #if we still don't have a Parser to use, guess based on the Domain (or IP)
540 0 0         unless ( $self->{ Parser } ) {
541 0           my $res = $self->guess_server_details ( $self->{ Domain } );
542 0           ( undef, $self->{ Parser } ) = @$res;
543             }
544              
545             #set these if they aren't already set
546 0 0         $self->{ Timeout } = $TIMEOUT unless $self->{ Timeout };
547 0 0         $self->{ Error } = $ERROR unless $self->{ Error };
548 0 0         $self->{ Retries } = $RETRIES unless $self->{ Retries };
549             }
550              
551             sub lookup {
552 0     0 1   my ( $self, %args ) = @_;
553            
554 0           $self->personality ( %args );
555              
556 0   0       my $cache = $args{ Cache } || ${ $self->{ _CACHE } };
557 0           $self->{ Domain }=~s/^www\.//; #trim leading www. if present; internic doesn't like it
558 0 0         print "looking up ", $self->{ Domain }, " on ", $self->{ Server }, "\n" if ($self->{ Verbose });
559            
560             #see if we already have a response in the cache, unless told not to
561 0 0         unless ( $self->{ Nocache } ) {
562             READCACHE: {
563 0 0         if ( -d $cache ) {
  0            
564 0 0         last READCACHE unless -e "$cache/$domain";
565 0           my $current = time ();
566 0   0       open D, "$cache/$domain" || last READCACHE;
567 0           my @stat = stat ( D );
568 0 0         if ( $current - $stat[ 9 ] > ${ $self->{ _EXPIRE } } ) {
  0            
569 0           close D;
570 0           last READCACHE;
571             }
572 0           undef $/; $self->{ Response } = ;
  0            
573 0           return 1;
574             }
575             }
576             }
577              
578             #connect to whois server
579 0           my $server = $self->{ Server };
580 0   0       my $suffix = $self->{ _ARGS }->{ $server }->{S} || '';
581 0   0       my $prefix = $self->{ _ARGS }->{ $server }->{P} || '';
582 0           my $sock = $self->_connect ( $self->{ Server } );
583 0 0         return undef unless $sock;
584            
585             #request whois info, then disconnect
586 0           print $sock $prefix , $self->{ Domain }, "$suffix\r\n";
587             #print $sock $prefix , $domain, "$suffix\r\n";
588 0           { local $/; undef $/; $self->{ Response } = <$sock>; }
  0            
  0            
  0            
589 0           close($sock); undef $sock;
  0            
590              
591             #did we get forwarded?
592 0           my $fw = eval { ($self->forwardwhois)[0] };
  0            
593 0           my @fwa = ();
594            
595             #if ($fw =~ m/\n/) {
596 0 0 0       unless (defined($fw) && $fw=~/whois/) { #if forwardwhois is a server, use it; otherwise...
597             #ARIN forwarding kludge 08/06/2002 rwoodard
598 0 0         if ( $self->{ Server } eq "whois.arin.net" ) {
    0          
599 0 0         $fw="whois.apnic.net" if ( $self->{ Response }=~/Asia Pacific Network Information (?:Center|Centre)/misg );
600 0 0         $fw="whois.ripe.net" if ( $self->{ Response }=~/European Regional Internet Registry|RIPE Network Coordination Centre/misg );
601            
602 0 0         $fw="whois.lacnic.net" if ( $self->{ Response }=~/Latin American and Caribbean IP address Regional Registry/misg );
603             }
604            
605             #APNIC forwarding kludge 08/06/2002 rwoodard
606             elsif ($self->{ Server } eq 'whois.apnic.net') {
607 0 0         $fw="whois.krnic.net" if ($self->{ Response }=~/Allocated to KRNIC/misg );
608 0 0         $fw="whois.twnic.net" if ($self->{ Response }=~/Allocated to TWNIC/misg );
609             }
610             else { #original code
611 0           @fwa = $self->{ Response } =~ m/\s+$self->{ Domain }\n.*?\n*?\s*?.*?Whois Server: (.*?)(?=\n)/isg;
612 0           $fw = shift @fwa;
613 0 0 0       return undef unless (defined($fw) && length($fw) > 0); # pattern not found
614             }
615            
616 0 0 0       return undef if (defined($fw) && $self->{ Server } eq $fw); #avoid infinite loop
617             }
618 0 0 0       if ( defined($fw) && $fw ne "" ) {
619 0           $self->personality( Format => $self->{_WHOIS_PARSER}->{$fw});
620 0 0         return undef if ($self->{ Server } eq $fw); #avoid infinite loop
621 0           $self->{ Server } = $fw; $self->{ Response } = "";
  0            
622             #$self->lookup();
623 0 0         print " forwarded to server $fw\n" if ($self->{ Verbose });
624 0           $self->lookup( Server => "$fw" ); #from Paul Fuchs
625             }
626              
627             #are there multiple netblocks? If so, do we pursue them? (ARIN only for now)
628 0 0 0       if ( $self->{Server} eq 'whois.arin.net' && $self->multiple && $self->{ Bottom_netblock } && $self->net_handle ) {
      0        
      0        
629 0           my @netblocks=($self->net_handle);
630 0           my $cnt=$#netblocks;
631             #print "mult blocks, looking up ", $netblocks[$cnt], " on ", $self->{ Server }, "\n";
632 0           $self->{ Response } = "";
633 0           $self->lookup( Domain => $netblocks[$cnt], Server => $self->{ Server });
634             }
635            
636             #cache the response
637 0 0 0       if ( (-d $cache) && (!($self->{Nocache})) ) {
638 0   0       open D, "> $cache/$domain" || return;
639 0           print D $self->{ Response };
640 0           close D;
641             }
642             #print "done with lookup\n";
643             }
644              
645             sub AUTOLOAD {
646              
647 0     0     my $self = shift;
648              
649 0 0         return undef unless $self->{ Response }; #we didn't get a response, nothing to return
650 0           my $key = $AUTOLOAD;
651 0           $key =~ s/.*://;
652              
653             #croak "Method $key not defined" unless exists ${$self->{ Parser }}{$key};
654 0 0         return undef unless exists ${$self->{ Parser }}{$key}; #don't croak(), just don't do anything
  0            
655            
656 0           my @matches = ();
657              
658 0 0         if ( ref(${$self->{ Parser } }{ $key }) !~ /^CODE/ ) { #not an array or hash, i.e. a regexp
  0            
659             #get everything in the response that matches the regexp; each match is an element in the array
660 0           @matches = $self->{ Response } =~ /${ $self->{ Parser } }{ $key }/sg;
  0            
661             #print "matches for $key: @matches\n";
662             }
663             else { #assumes you have defined your own subroutine with register_parser, pass the whole response to it
664 0           @matches = &{ $self->{ Parser }{$key}}($self->response);
  0            
665             }
666              
667 0           my @tmp = split /\n/, join "\n", @matches;
668 0           for (@tmp) { s/^\s+//; s/\s+$//; chomp }; #trim leading/trailing whitespace and newline
  0            
  0            
  0            
669             #print "tmp: @tmp\n";
670             #depending on calling context, return an array or a newline-delimited string
671 0 0         return wantarray ? @tmp : join "\n", @tmp ;
672              
673             }
674              
675             sub response {
676              
677 0     0 0   my $self = shift;
678 0           return $self->{ Response };
679              
680             }
681              
682             sub _connect {
683              
684 0     0     my $self = shift;
685 0           my $machine = shift;
686 0           my $error = $self->{Error};
687 0           my $maxtries = $self->{Retries};
688 0           my $sock;
689 0           my $retries=0;
690            
691 0   0       until ($sock || $retries == $maxtries) {
692             #print " connecting to $machine\n";
693 0           $sock = new IO::Socket::INET PeerAddr => $machine,
694             PeerPort => 'whois',
695             Proto => 'tcp',
696             Timeout => $self->{Timeout};
697             # or &$error( "[$@]" );
698 0 0         $retries++ unless ($sock);
699 0 0 0       print "try $retries failed\n" if ( $self->{ Verbose } && !$sock);
700             }
701 0 0         &$error( "[$@]" ) unless ($sock);
702            
703 0 0         $sock->autoflush if $sock;
704 0           return $sock;
705             }
706              
707 0     0 0   sub ignore {}
708              
709 0     0     sub DESTROY {} #from Gregory Karpinsky
710              
711             'True Value.';
712              
713              
714             =head1 NAME
715              
716             Net::XWhois - Whois Client Interface for Perl5.
717              
718             =head1 SYNOPSIS
719              
720             use Net::XWhois;
721              
722             $whois = new Net::XWhois Domain => "vipul.net" ;
723             $whois = new Net::XWhois Domain => "bit.ch",
724             Server => "domreg.nic.ch",
725             Retain => 1,
726             Parser => {
727             nameservers => 'nserver:\s+(\S+)',
728             };
729              
730             =head1 DESCRIPTION
731              
732             The Net::XWhois class provides a generic client framework for doing Whois
733             queries and parsing server response.
734              
735             The class maintains an array of top level domains and whois servers
736             associated with them. This allows the class to transparently serve
737             requests for different tlds, selecting servers appropriate for the tld.
738             The server details are, therefore, hidden from the user and "vipul.net"
739             (from InterNIC), gov.ru (from RIPE) and "bit.ch" (from domreg.nic.ch) are
740             queried in the same manner. This behaviour can be overridden by specifying
741             different bindings at object construction or by registering associations
742             with the class. See L<"register_associations()"> and L<"new()">.
743              
744             One of the more important goals of this module is to enable the design of
745             consistent and predictable interfaces to incompatible whois response
746             formats. The Whois RFC (954) does not define a template for presenting
747             server data; consequently there is a large variation in layout styles as
748             well as content served across servers.
749              
750             (There is, however, a new standard called RPSL (RFC2622) used by RIPE
751             (http://www.ripe.net), the European main whois server.)
752              
753             To overcome this, Net::XWhois maintains another set of tables - parsing
754             rulesets - for a few, popular response formats. (See L<"%PARSERS">). These
755             parsing tables contain section names (labels) together with regular
756             expressions that I the corresponding section text. The section text
757             is accessed "via" labels which are available as data instance methods at
758             runtime. By following a consistent nomenclature for labels, semantically
759             related information encoded in different formats can be accessed with the
760             same methods.
761              
762             =head1 CONSTRUCTOR
763              
764             =over 4
765              
766             =item new ()
767              
768             Creates a Net::XWhois object. Takes an optional argument, a hash, that
769             specifies the domain name to be queried. Calls lookup() if a name is
770             provided. The argument hash can also specify a whois server, a parsing
771             rule-set or a parsing rule-set format. (See L<"personality()">). Omitting
772             the argument will create an "empty" object that can be used for accessing
773             class data.
774              
775             =item personality ()
776              
777             Alters an object's personality. Takes a hash with following arguments.
778             (Note: These arguments can also be passed to the constructor).
779              
780             =over 8
781              
782             =item B
783              
784             Domain name to be queried.
785              
786             =item B
787              
788             Server to query.
789              
790             =item B
791              
792             Parsing Rule-set. See L<"%PARSERS">.
793              
794             Parser => {
795             name => 'domain:\s+(\S+)\n',
796             nameservers => 'nserver:\s+(\S+)',
797             contact_emails => 'e-mail:\s+(\S+\@\S+)',
798             };
799              
800              
801             =item B
802              
803             A pre-defined parser format like INTERNIC, INTERNIC_FORMAT, RIPE,
804             RIPE_CH, JAPAN etc.
805              
806             Format => 'INTERNIC_CONTACT',
807              
808             =item B
809              
810             Force XWhois to ignore the cached records.
811              
812             =item B
813              
814             Determines how a network connection error is handled. By default Net::XWhois
815             will croak() if it can't connect to the whois server. The Error attribute
816             specifies a function call name that will be invoked when a network
817             connection error occurs. Possible values are croak, carp, confess (imported
818             from Carp.pm) and ignore (a blank function provided by Net::XWhois). You
819             can, of course, write your own function to do error handling, in which case
820             you'd have to provide a fully qualified function name. Example:
821             main::logerr.
822              
823             =item B
824              
825             Timeout value for establishing a network connection with the server. The
826             default value is 60 seconds.
827              
828             =back
829              
830             =back
831              
832             =head1 CLASS DATA & ACCESS METHODS
833              
834             =over 4
835              
836             =item %PARSERS
837              
838             An associative array that contains parsing rule-sets for various response
839             formats. Keys of this array are format names and values are hash refs that
840             contain section labels and corresponding parser code. The parser code can
841             either be a regex or a reference to a subroutine. In the case of a
842             subroutine, the whois 'response' information is available to the sub in
843             $_[0]. Parsers can be added and extended with the register_parser() method.
844             Also see L.
845              
846             my %PARSERS = (
847             INTERNIC => {
848             contact_tech => 'Technical Contact.*?\n(.*?)(?=\...
849             contact_zone => 'Zone Contact.*?\n(.*?)(?=\s*\n[...
850             contact_billing => 'Billing Contact.*?\n(.*?)(?=\s*...
851             contact_emails => \&example_email_parser
852             },
853             { etc. ... },
854             );
855              
856             sub example_email_parser {
857              
858             # Note that the default internal implemenation for
859             # the INTERNIC parser is not a user-supplied code
860             # block. This is just an instructive example.
861              
862             my @matches = $_[0] =~ /(\S+\@\S+)/sg;
863             return @matches;
864             }
865              
866             See XWhois.pm for the complete definition of %PARSERS.
867              
868             =item %WHOIS_PARSER
869              
870             %WHOIS_PARSER is a table that associates each whois server with their output format.
871              
872             my %WHOIS_PARSER = (
873             'whois.ripe.net' => 'RPSL',
874             'whois.nic.mil' => 'INTERNIC',
875             'whois.nic.ad.jp' => 'JAPAN',
876             'whois.domainz.net.nz' => 'GENERIC',
877             'whois.nic.gov' => 'INTERNIC',
878             'whois.nic.ch' => 'RIPE_CH',
879             'whois.twnic.net' => 'TAIWAN',
880             'whois.internic.net' => 'INTERNIC',
881             'whois.nic.net.sg' => 'RIPE',
882             'whois.aunic.net' => 'RIPE',
883             'whois.cdnnet.ca' => 'CANADA',
884             'whois.nic.uk' => 'INTERNIC',
885             'whois.krnic.net' => 'KOREA',
886             'whois.isi.edu' => 'INTERNIC',
887             'whois.norid.no' => 'RPSL',
888             ( etc.....)
889              
890             Please note that there is a plethora of output formats, allthough there
891             are RFCs on this issue, like for instance RFC2622, there are numerous
892             different formats being used!
893              
894             =item %DOMAIN_ASSOC
895              
896             %DOMAIN_ASSOC is a table that associates top level domain names with their
897             respective whois servers. You'd need to modity this table if you wish to
898             extend the module's functionality to handle a new set of domain names. Or
899             alter existing information. I provides an
900             interface to this array. See XWhois.pm for the complete definition.
901              
902             my %DOMAIN_ASSOC = (
903             'al' => 'whois.ripe.net',
904             'am' => 'whois.ripe.net',
905             'at' => 'whois.ripe.net',
906             'au' => 'whois.aunic.net',
907             'az' => 'whois.ripe.net',
908             'ba' => 'whois.ripe.net',
909             'be' => 'whois.ripe.net',
910              
911              
912             =item register_parser()
913              
914             Extend, modify and override entries in %PARSERS. Accepts a hash with three
915             keys - Name, Retain and Parser. If the format definition for the specified
916             format exists and the Retain key holds a true value, the keys from the
917             specified Parser are added to the existing definition. A new definition is
918             created when Retain is false/not specified.
919              
920             my $w = new Net::Whois;
921             $w->register_parser (
922             Name => "INTERNIC",
923             Retain => 1,
924             Parser => {
925             creation_time => 'created on (\S*?)\.\n',
926             some_randome_entity => \&random_entity_subroutine
927             };
928              
929             Instructions on how to create a workable random_entity_subroutine are
930             availabe in the I<%PARSERS> description, above.
931              
932             =item register_association()
933              
934             Override and add entries to %ASSOC. Accepts a hash that contains
935             representation specs for a whois server. The keys of this hash are server
936             machine names and values are list-refs to the associated response formats
937             and the top-level domains handled by the servers. See Net/XWhois.pm for
938             more details.
939              
940             my $w = new Net::XWhois;
941             $w->register_association (
942             'whois.aunic.net' => [ RIPE, [ qw/au/ ] ]
943             );
944              
945             =item register_cache()
946              
947             By default, Net::XWhois caches all whois responses and commits them, as
948             separate files, to /tmp/whois. register_cache () gets and sets the cache
949             directory. Setting to "undef" will disable caching.
950              
951             $w->register_cache ( "/some/place/else" );
952             $w->register_cache ( undef );
953              
954             =back
955              
956             =head1 OBJECT METHODS
957              
958             =over 4
959              
960             =item B
961              
962             Access to the whois response data is provided via AUTOLOADED methods
963             specified in the Parser. The methods return scalar or list data depending
964             on the context.
965              
966              
967             Internic Parser provides the following methods:
968              
969             =over 8
970              
971             =item B
972              
973             Domain name.
974              
975             =item B
976              
977             Domain Status when provided. When the domain is on hold, this
978             method will return "On Hold" string.
979              
980             =item B
981              
982             Nameservers along with their IPs.
983              
984             =item B
985              
986             Registrant's name and address.
987              
988             =item B
989              
990             Administrative Contact.
991              
992             =item B
993              
994             Technical Contact.
995              
996             =item B
997              
998             Zone Contact.
999              
1000             =item B
1001              
1002             Billing Contact.
1003              
1004             =item B
1005              
1006             List of email addresses of contacts.
1007              
1008             =item B
1009              
1010             List of contact handles in the response. Contact and Domain handles
1011             are valid query data that can be used instead of contact and domain
1012             names.
1013              
1014             =item B
1015              
1016             List of domain handles in the response. Can be used for sorting
1017             out reponses that contain multiple domain names.
1018              
1019             =back
1020              
1021             =item B
1022              
1023             Does a whois lookup on the specified domain. Takes the same arguments as
1024             new().
1025              
1026             my $w = new Net::XWhois;
1027             $w->lookup ( Domain => "perl.com" );
1028             print $w->response ();
1029              
1030             =back
1031              
1032             =head1 EXAMPLES
1033              
1034             Look at example programs that come with this package. "whois" is a
1035             replacement for the standard RIPE/InterNIC whois client. "creation"
1036             overrides the Parser value at object init and gets the Creation Time of an
1037             InterNIC domain. "creation2" does the same thing by extending the Class
1038             Parser. "contacts" queries and prints information about domain's
1039             Tech/Billing/Admin contacts.
1040              
1041             contribs/ containts parsers for serveral whois servers, which have not been
1042             patched into the module.
1043              
1044             =head1 AUTHOR
1045              
1046             Vipul Ved Prakash
1047              
1048             =head1 THANKS
1049              
1050             Curt Powell , Matt Spiers
1051             , Richard Dice , Robert Chalmers
1052             , Steinar Overbeck Cook , Steve
1053             Weathers , Robert Puettmann ,
1054             Martin H . Sluka" , Rob Woodard ,
1055             Jon Gilbert, Erik Aronesty for patches, bug-reports and many cogent
1056             suggestions.
1057              
1058             =head1 MAILING LIST
1059              
1060             Net::XWhois development has moved to the sourceforge mailing list,
1061             xwhois-devel@lists.sourceforge.net. Please send all Net::XWhois related
1062             communication directly to the list address. The subscription interface is
1063             at: http://lists.sourceforge.net/mailman/listinfo/xwhois-devel
1064              
1065             =head1 SEE ALSO
1066              
1067             RFC 954
1068             RFC 2622
1069              
1070             =head1 COPYRIGHT
1071              
1072             Copyright (c) 1998-2001 Vipul Ved Prakash. All rights reserved. This
1073             program is free software; you can redistribute it and/or modify it under
1074             the same terms as Perl itself.