aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--whoseip/Makefile.PL4
-rw-r--r--whoseip/Whoseip/DB.pm227
-rw-r--r--whoseip/whoseip.pl47
3 files changed, 230 insertions, 48 deletions
diff --git a/whoseip/Makefile.PL b/whoseip/Makefile.PL
index 9d1bb4d..395e359 100644
--- a/whoseip/Makefile.PL
+++ b/whoseip/Makefile.PL
@@ -21,7 +21,9 @@ use ExtUtils::AutoInstall (
'IO::Socket' => 1.34,
'Pod::Usage' => 1.51,
'Pod::Man' => 2.25,
- 'Net::CIDR' => 0.14
+ 'Net::CIDR' => 0.14,
+ 'Data::UUID' => 1.219,
+ 'Storable' => 2.34
]
);
diff --git a/whoseip/Whoseip/DB.pm b/whoseip/Whoseip/DB.pm
index 83151e1..0abc953 100644
--- a/whoseip/Whoseip/DB.pm
+++ b/whoseip/Whoseip/DB.pm
@@ -17,24 +17,25 @@
package Whoseip::DB;
use strict;
-use Fcntl qw(SEEK_SET SEEK_CUR);
+use Fcntl qw(SEEK_SET SEEK_CUR :flock);
use Socket qw(inet_ntoa);
use Storable qw(freeze thaw);
+use Data::UUID;
use Carp;
require Exporter;
our @ISA = qw(Exporter);
-our %EXPORT_TAGS = ( 'all' => [ qw(ipdb_open ipdb_lookup ipdb_insert ipdb_close) ] );
+our %EXPORT_TAGS = ( 'all' => [ qw(ipdb_open ipdb_lookup ipdb_insert
+ ipdb_sync ipdb_locker ipdb_close) ] );
-our @EXPORT_OK = ( qw(ipdb_open ipdb_lookup ipdb_insert ipdb_close) );
+our @EXPORT_OK = ( qw(ipdb_open ipdb_lookup ipdb_insert
+ ipdb_sync ipdb_locker ipdb_close) );
our @EXPORT = qw();
our $VERSION = "0.1";
-1;
-
=pod
=head1 NAME
@@ -71,10 +72,11 @@ B<Offset> B<Size> B<Description>
0 8 "WHOSEIP\0"
8 2 major version
10 2 minor version
- 12 4 page size
- 16 4 number of allocated pages
- 20 4 number of entries in root index table
- 24 488 root index table
+ 12 16 UUID
+ 28 4 page size
+ 32 4 number of allocated pages
+ 36 4 number of entries in root index table
+ 40 472 root index table
The first three fields serve to identify the file format and its version.
At the time of this writing, major and minor versions are B<1>.B<0>.
@@ -90,7 +92,7 @@ database. Each entry in this table consists of two 32-bit words: the
first one keeps the length of the IP address in bits (e.g. 32 for IPv4
and 128 for IPv6), and the second one keeps the offset of the first
index table for entries of that size. The table can accomodate at most
-122 entries, which is more than enough for the purpose.
+59 entries, which is more than enough for the purpose.
An B<index page> contains a table of offsets of the next page to look up
(whether index or leaf) and is indexed by the octet value (0 -- 255).
@@ -189,7 +191,12 @@ sub ipdb_open {
my $fd;
if (-e $filename) {
my $mode;
- if (-w $filename) {
+
+ if ($_{mode} eq 'ro') {
+ $ipdbfile{mode} = "<";
+ } elsif ($_{mode} eq 'rw') {
+ $ipdbfile{mode} = "+<";
+ } elsif (-w $filename) {
$ipdbfile{mode} = "+<";
} else {
$ipdbfile{mode} = "<";
@@ -199,14 +206,15 @@ sub ipdb_open {
binmode $fd;
croak "$filename is not a valid IP cache file"
unless sysread($fd, my $s, 512) == 512;
- my ($sign,$maj,$min,$size,$np,$count,@tab) =
- unpack('Z8 S S L L L L*', $s);
+ my ($sign,$maj,$min,$uuid,$size,$np,$count,@tab) =
+ unpack('Z8 S S a16 L L L L*', $s);
croak "$filename is not a valid IP cache file"
unless $sign eq $dbsign;
croak "$filename is of wrong version ($maj.$min, expected $vmajor.$vminor)"
unless ($maj == $vmajor and $min == $vminor);
croak "$filename: page size too small ($size)" if ($size < 1032);
+ $ipdbfile{uuid} = $uuid;
$ipdbfile{pagesize} = $size;
$ipdbfile{numpages} = $np;
@@ -221,7 +229,11 @@ sub ipdb_open {
binmode $fd;
$ipdbfile{pagesize} = defined($_{pagesize}) ? $_{pagesize} : 1280;
$ipdbfile{numpages} = 0;
- syswrite($fd, pack('Z8 S S L L L @512', $dbsign, $vmajor, $vminor,
+ my $ug = new Data::UUID;
+ my $uuid = $ug->create();
+ $ipdbfile{uuid} = $uuid;
+ syswrite($fd, pack('Z8 S S a16 L L L @512',
+ $dbsign, $vmajor, $vminor, $uuid,
$ipdbfile{pagesize}, 0, 0));
}
$ipdbfile{filename} = $filename;
@@ -229,9 +241,101 @@ sub ipdb_open {
$ipdbfile{maxpagecache} =
defined($_{maxpagecache}) ? $_{maxpagecache} : 16;
$ipdbfile{debug} = $_{debug};
+ if ($ipdbfile{debug}) {
+ my $ug = new Data::UUID;
+ print STDERR "file $filename, UUID ".$ug->to_string($ipdbfile{uuid})."\n";
+ }
return \%ipdbfile;
}
+=pod
+
+=head2 B<Whoseip::DB::ipdb_locker(I<$dbf>>[B<, I<opts>>]B<);>
+
+Lock or unlock the database.
+
+I<opts> is a hash of the following options:
+
+=over 4
+
+=item B<lock> => I<MODE>
+
+Defines the locking operation. I<MODE> is one of: B<exclusive> or B<LOCK_EX>,
+B<shared> or B<LOCK_SH>, B<unlock> or B<LOCK_UN>.
+
+If this option is not supplied, no locking will be done. This is useful
+to force syncronization with the disk state.
+
+=item B<sync> => B<0>|B<1>
+
+When set to B<0>, disables synchronization with the disk file. Default is
+B<1>.
+
+=cut
+sub ipdb_locker {
+ my ($dbf) = shift;
+ local %_ = @_;
+
+ my $mode;
+
+ if ($_{lock} eq 'exclusive') {
+ $mode = LOCK_EX;
+ } elsif ($_{lock} eq 'shared') {
+ $mode = LOCK_SH;
+ } elsif ($_{lock} eq 'unlock') {
+ $mode = LOCK_UN;
+ } else {
+ $mode = $_{lock};
+ }
+
+ if (defined($mode)) {
+ flock($dbf->{fd}, $mode) or do {
+ carp "$dbf->{filename}: can't lock: $!";
+ return 0;
+ };
+
+ if ($mode == LOCK_UN) {
+ delete $dbf->{lockmode};
+ } else {
+ $dbf->{lockmode} = $mode;
+ }
+ return 1 if $mode == LOCK_UN;
+ }
+
+ return 1 if (defined($_{sync} and $_{sync} == 0));
+
+ if (sysseek($dbf->{fd}, 0, SEEK_SET) != 0) {
+ croak "$dbf->{filename}: can't seek: $!";
+ }
+
+ croak "$dbf->{filename}: read error: $!"
+ unless sysread($dbf->{fd}, my $s, 512) == 512;
+
+ my ($sign,$maj,$min,$uuid,$size,$np,$count,@tab) =
+ unpack('Z8 S S a16 L L L L*', $s);
+
+ if ($uuid ne $dbf->{uuid}) {
+ print STDERR "$dbf->{filename}: disk file has changed\n"
+ if $dbf->{debug};
+
+ # Re-initialize DB info
+ $dbf->{uuid} = $uuid;
+ $dbf->{pagesize} = $size;
+ $dbf->{numpages} = $np;
+
+ for (my $i = 0; $i < $count; $i += 2) {
+ $dbf->{rootidx}{$tab[$i]} = $tab[$i+1];
+ print STDERR "ROOTIDX $tab[$i]=$tab[$i+1]\n"
+ if $_{debug};
+ }
+
+ # Invalidate the cache
+ ipdb_cache_invalidate($dbf);
+ }
+
+ return 1;
+}
+
sub ipdb_save_page($$) {
my ($dbf, $page) = @_;
@@ -272,12 +376,11 @@ sub ipdb_save_page($$) {
++$i;
}
$ret = syswrite($dbf->{fd},
- pack('LLLa*.',
+ pack('LLLa*@'.$dbf->{pagesize},
$page->{type},
- $#{$page->{tab}} + 1,
+ $#a + 1,
$page->{next},
- @a,
- $dbf->{pagesize}));
+ @a));
} else {
croak "unrecognized page type ($page->{type})";
}
@@ -287,6 +390,12 @@ sub ipdb_save_page($$) {
delete $page->{dirty};
}
+sub ipdb_cache_invalidate($) {
+ my $dbf = shift;
+
+ $dbf->{pagecache} = ();
+}
+
sub ipdb_cache_put($$) {
my ($dbf,$page) = @_;
if (keys(%{$dbf->{pagecache}}) >= $dbf->{maxpagecache}) {
@@ -333,37 +442,57 @@ sub ipdb_cache_get($$) {
=pod
-=head2 B<Whoseip::DB::ipdb_close(I<$dbf>);>
+=head2 B<Whoseip::DB::ipdb_sync(I<$dbf>);>
-Close the database. I<$dbf> is the handle returned from the
-previous call to B<ipdb_open>.
-
-=cut
-sub ipdb_close($) {
+Sunchronizes the database with the disk.
+
+=cut
+
+sub ipdb_sync($) {
my $dbf = shift;
if ($dbf->{modified}) {
croak "$dbf->{filename}: can't seek: $!"
if (sysseek($dbf->{fd}, 0, SEEK_SET) != 0);
+
+ my $ug = new Data::UUID;
+ $dbf->{uuid} = $ug->create();
+
my $n = syswrite($dbf->{fd},
- pack('Z8 S S L L L L* @512',
- $dbsign, $vmajor, $vminor,
+ pack('Z8 S S a16 L L L L* @512',
+ $dbsign, $vmajor, $vminor, $dbf->{uuid},
$dbf->{pagesize}, $dbf->{numpages},
keys(%{$dbf->{rootidx}})+0,
map { $_, $dbf->{rootidx}{$_} }
keys %{$dbf->{rootidx}}));
croak "$dbf->{filename}: write error at header: $n: $!"
unless ($n == 512);
+ $dbf->{modified} = 0;
}
while (my ($off, $page) = each %{$dbf->{pagecache}}) {
ipdb_save_page($dbf, $page) if $page->{dirty};
}
+}
+
+=pod
+
+=head2 B<Whoseip::DB::ipdb_close(I<$dbf>);>
+
+Close the database. I<$dbf> is the handle returned from the
+previous call to B<ipdb_open>.
+
+=cut
+sub ipdb_close($) {
+ my $dbf = shift;
+ ipdb_locker($dbf, lock => LOCK_EX, sync => 0);
+ ipdb_sync($dbf);
+ ipdb_locker($dbf, lock => LOCK_UN);
close $dbf->{fd};
}
sub ipdb_get_page($$) {
my ($dbf,$off) = @_;
my %ret;
-
+
if (sysseek($dbf->{fd}, $off, SEEK_SET) != $off) {
croak "$dbf->{filename}: can't seek: $!";
}
@@ -433,11 +562,6 @@ sub ipdb_get_root_page($$) {
return $p;
}
-### FIXME: Declared in whoseip.pl
-my $ipv4rx = '\d{1,3}((\.\d{1,3}){3})';
-
-###
-
=pod
@@ -466,13 +590,13 @@ Network mask in a dotted-quad form.
If not found, the function returns B<undef>.
=cut
-sub ipdb_lookup($$) {
+sub ipdb_lookup_unlocked($$) {
my ($dbf,$ipstr) = @_;
my @ipo;
my $ipn;
my $nbits;
- if ($ipstr =~ /^$ipv4rx$/) {
+ if ($ipstr =~ /^\d{1,3}((\.\d{1,3}){3})$/) {
@ipo = split(/\./, $ipstr);
$ipn = ($ipo[0] << 24) + ($ipo[1] << 16) + ($ipo[2] << 8) + $ipo[3];
$nbits = 32;
@@ -515,7 +639,23 @@ sub ipdb_lookup($$) {
$page = ipdb_cache_get($dbf, $page->{next});
}
}
-
+
+sub ipdb_lookup($$) {
+ my ($dbf) = @_;
+
+ if ($dbf->{lockmode} == LOCK_EX) {
+ return &ipdb_lookup_unlocked;
+ } elsif ($dbf->{lockmode} == LOCK_SH) {
+ ipdb_locker($dbf, sync => 1);
+ return &ipdb_lookup_unlocked;
+ } else {
+ ipdb_locker($dbf, lock => LOCK_SH);
+ my %res = &ipdb_lookup_unlocked;
+ ipdb_locker($dbf, lock => LOCK_UN);
+ return %res;
+ }
+}
+
=pod
=head2 $res = B<Whoseip::DB::ipdb_insert(I<$dbf>, I<$cidr>, I<$country>);>
@@ -525,7 +665,7 @@ Inserts into the database I<$cidr> and the corresponding country code I<$country
Currently, I<$cidr> must be in the form B<I<Net-address>/I<Netmask-length>>.
=cut
-sub ipdb_insert {
+sub ipdb_insert_unlocked {
my $dbf = shift;
my $cidr = shift;
my $country = shift;
@@ -580,4 +720,17 @@ sub ipdb_insert {
return 1;
}
-
+sub ipdb_insert {
+ my ($dbf) = @_;
+
+ return &ipdb_insert_unlocked if ($dbf->{lockmode});
+
+ ipdb_locker($dbf, lock => LOCK_EX);
+ my $res = &ipdb_insert_unlocked;
+ ipdb_sync($dbf);
+ ipdb_locker($dbf, lock => LOCK_UN);
+ return $res;
+}
+
+1;
+
diff --git a/whoseip/whoseip.pl b/whoseip/whoseip.pl
index e267a4c..9ca899e 100644
--- a/whoseip/whoseip.pl
+++ b/whoseip/whoseip.pl
@@ -341,6 +341,19 @@ sub nic_or_kr_decode {
}
}
+sub nobistech_decode {
+ my ($input, $ref) = @_;
+
+ if ($input =~ /network:IP-Network:(.+)/) {
+ $ref->{cidr} = $1;
+ $ref->{range} = cidr_to_range($1);
+ $ref->{count} = range2count($ref->{range});
+ } elsif ($input =~ /network:Country-Code:(.+)/) {
+ $ref->{country} = $1;
+ }
+}
+
+
# #######################################################################
# Server table
# #######################################################################
@@ -353,7 +366,8 @@ my %srvtab = (
'whois.twnic.net' => { d => \&twnic_decode },
'whois.nic.ad.jp' => { q => \&nic_ad_jp_fmt, d => \&nic_ad_jp_decode },
'whois.nic.br' => { d => \&lacnic_decode },
- 'whois.nic.or.kr' => { d => \&nic_or_kr_decode },
+ 'whois.nic.or.kr' => { d => \&nic_or_kr_decode },
+ 'rwhois.nobistech.net' => { d => \&nobistech_decode }
);
sub format_query {
@@ -450,10 +464,14 @@ sub serve {
my $srv = findsrv($term);
if (defined($srv) and $srv ne 'UNKNOWN') {
+ my %prev;
while (%res = whois($term, $srv),
and defined($res{referto})) {
+ %prev = %res if $res{status} = 'OK';
$srv = $res{referto};
}
+ %res = %prev
+ if (!defined($res{country}) and defined($prev{country}));
if (!defined($res{country})) {
$res{status} = 'NO';
$res{diag} = 'IP unknown';
@@ -641,6 +659,7 @@ sub docgi {
my $output_format;
my $fastcgi;
+my $single_query;
if (defined($ENV{WHOSEIP_CONF})) {
read_config_file($ENV{WHOSEIP_CONF});
@@ -676,7 +695,8 @@ GetOptions("h" => sub {
},
"fastcgi:s" => \$fastcgi,
"cache-file|c:s" => \$dbfile,
- "no-cache|N" => sub { $dbfile = undef; }
+ "no-cache|N" => sub { $dbfile = undef; },
+ "single-query" => \$single_query
) or exit(EX_USAGE);
if (defined($dbfile)) {
@@ -731,16 +751,19 @@ if ($fastcgi) {
my $term;
my %res;
+ ipdb_locker($dbf, lock => 'shared') if (defined($dbf));
$output_format = $fmtab{unix} unless defined($output_format);
if ($#ARGV == -1) {
unless (-t *STDIN) {
local $/ = CRLF;
$delim = "$CR$LF";
}
- $term = <>;
- chomp $term;
- %res = serve($term);
- format_out($output_format, %res);
+ while (<>) {
+ chomp;
+ %res = serve($_);
+ format_out($output_format, %res);
+ last if $single_query;
+ }
} else {
foreach my $term (@ARGV) {
format_out($output_format, serve($term));
@@ -748,9 +771,7 @@ if ($fastcgi) {
}
}
-if (defined($dbf)) {
- ipdb_close($dbf);
-}
+ipdb_close($dbf) if defined($dbf);
__END__
=head1 NAME
@@ -773,6 +794,7 @@ B<whoiseip>
[B<--formfile=>I<FILE>]
[B<--help>]
[B<--ip-list=>I<FILE>]
+[B<--single-query>]
[B<--usage>]
[I<IPADDR>...]
@@ -949,7 +971,12 @@ I<CIDR> I<SERVER>
Comments are introduced with a B<#> sign. Empty lines are ignored.
Without this option, B<whoseip> uses the built-in list of servers.
-
+
+=item B<--single-query>
+
+This option is valid only in B<inetd mode>. It instructs B<whoseip> to
+terminate after replying to the first query.
+
=back
The following options cause the program to display informative text and

Return to:

Send suggestions and report system problems to the System administrator.