#!/usr/bin/perl

# Copyright (C) 2006-2007 Unregistered DNS
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

sub inet_aton {
	my $SourceStr=$_[0];

	my @SourceStrArray = split(/\./, $SourceStr);
	if ($#SourceStrArray < 3) {
		die("Error: Address should have the format \"aa.bb.cc.dd\" " . $#SourceStrArray . ".");
	}

	my $DestValue = @SourceStrArray[0];
	$DestValue <<= 8;
	$DestValue |= @SourceStrArray[1];
	$DestValue <<= 8;
	$DestValue |= @SourceStrArray[2];
	$DestValue <<= 8;
	$DestValue |= @SourceStrArray[3];

	return $DestValue;
}

sub addrule {
	my $Address=$_[0];
	my $AddressCount=$_[1];
	my $Country=$_[2];

	my $SubnetMaskBin = sprintf "%b", scalar($AddressCount);
	my $SubnetMaskBinCnt = $SubnetMaskBin;
	$SubnetMaskBinCnt =~ s/1//g;
	my $SubnetBit = (32 - length($SubnetMaskBinCnt));

	# calc subnet mask
	my $SubnetMaskBit=(1 << 31);
	my $SubnetMaskVal=0;
	for($j=0;$j<$SubnetBit;$j++) {
		$SubnetMaskVal|=$SubnetMaskBit;
		$SubnetMaskBit>>=1;
	}
	my $SubnetMaskVal1=($SubnetMaskVal >> 24)&255;
	my $SubnetMaskVal2=($SubnetMaskVal >> 16)&255;
	my $SubnetMaskVal3=($SubnetMaskVal >> 8)&255;
	my $SubnetMaskVal4=$SubnetMaskVal&255;

	# add it
	my @NicDatum = (inet_aton($Address),
		inet_aton($SubnetMaskVal1 . "." . $SubnetMaskVal2 . "." . $SubnetMaskVal3 . "." . $SubnetMaskVal4),
		$Country);
	push @NicDataArray, [ @NicDatum ];
}

if ($#ARGV < 0) {
	print STDERR "Usage: countrycheck.pl (*NIC data file) ... < (IP address list file)\n";
	print STDERR "\n";
	print STDERR "(*NIC data file): Data files which provided by regional internet registry such as.\n";
	print STDERR "                  ARIN, APNIC, RIPE, LACNIC and AfriNIC (or more in future).\n";
	print STDERR "\n";
	print STDERR "(IP address list file): text file which contains IP addresses (CR/LF separated).\n";
	exit;
}

@NicDataArray = ();

for(local $i=0;$i<=$#ARGV;$i++) {
	print STDERR "Reading #" . ($i+1) . "/" . ($#ARGV+1) . " " . @ARGV[$i] . "...";

	open(DATAFILE, "<" . $ARGV[$i]);

	while (<DATAFILE>) {
		@TextArray = split(/\|/, $_);

		# parse only ipv4, ipv6
		if ((lc(@TextArray[2]) eq "ipv4") && (@TextArray[3] ne "*")) {
			# check country
			$found=0;

			# check whether number of addresses is CIDR-ready or not.
			$SubnetMaskBin = sprintf "%b", scalar(@TextArray[4]);
			$SubnetMaskBinCnt = $SubnetMaskBin;
			$SubnetMaskBinCnt =~ s/0//g;

			if (length($SubnetMaskBinCnt) > 1) {
				# print "$CommentChar Notice: @TextArray[3]/\#@TextArray[4] ($SubnetMaskBin) is not CIDR-ready number; splitted.\n";
			}

			@AddressArray = split(/\./, @TextArray[3]);
			if ($#AddressArray < 3) {
				print "$CommentChar Error: \"@TextArray[3]\" should have 4 fields.\n";
				last;
			}
			$AddressValue = scalar(@AddressArray[0]) << 24;
			$AddressValue |= scalar(@AddressArray[1]) << 16;
			$AddressValue |= scalar(@AddressArray[2]) << 8;
			$AddressValue |= scalar(@AddressArray[3]);

			for (local $i=0;$i<length($SubnetMaskBin);$i++) {
				if (substr($SubnetMaskBin, $i, 1) eq "1") {
					$AddressCount = 2 ** (length($SubnetMaskBin) - $i - 1);

					if (($AddressValue & ($AddressCount - 1)) > 0) {
						$AddressStr = sprintf "%d.", ($AddressValue >> 24) & 255;
						$AddressStr .= sprintf "%d.", ($AddressValue >> 16) & 255;
						$AddressStr .= sprintf "%d.", ($AddressValue >> 8) & 255;
						$AddressStr .= sprintf "%d", $AddressValue & 255;

						# print "$CommentChar Notice: $AddressStr/\#$AddressCount goes across the subnet boundary. splitted.\n";

						while ($AddressCount > 0) {
							$AddressCountSub = $AddressCount;

							while (($AddressValue & ($AddressCountSub - 1)) > 0) {
								$AddressCountSub /= 2;
							}

							$AddressStr = sprintf "%d.", ($AddressValue >> 24) & 255;
							$AddressStr .= sprintf "%d.", ($AddressValue >> 16) & 255;
							$AddressStr .= sprintf "%d.", ($AddressValue >> 8) & 255;
							$AddressStr .= sprintf "%d", $AddressValue & 255;

							addrule($AddressStr, $AddressCountSub, @TextArray[1]);
	
							$AddressValue += $AddressCountSub;
							$AddressCount -= $AddressCountSub;
						}
					} else {
						$AddressStr = sprintf "%d.", ($AddressValue >> 24) & 255;
						$AddressStr .= sprintf "%d.", ($AddressValue >> 16) & 255;
						$AddressStr .= sprintf "%d.", ($AddressValue >> 8) & 255;
						$AddressStr .= sprintf "%d", $AddressValue & 255;
						$AddressCount = 2 ** (length($SubnetMaskBin) - $i - 1);

						addrule($AddressStr, $AddressCount, @TextArray[1]);
	
						$AddressValue += $AddressCount;
					}
				}
			}
		} elsif (lc(@TextArray[3]) eq "ipv6") {
		} elsif (lc(@TextArray[0]) eq "2") {
			# database version
			print STDERR " Database version " . @TextArray[2] . " ";
		}
	}

	close(DATAFILE);

	print STDERR "done.\n";
}

print STDERR "... Data count " . $#NicDataArray . "\n";

print STDERR "Processing ";
$prevpos = 0;
while (<STDIN>) {
	print STDERR ".";

	$TargetAddrStr = $_;
	$TargetAddrStr =~ s/\r$//;
	$TargetAddrStr =~ s/\n$//;
	$TargetAddr = inet_aton($TargetAddrStr);

	for (local $i=$prevpos;$i<=$#NicDataArray;$i++) {
		if (($TargetAddr & $NicDataArray[$i][1]) == ($NicDataArray[$i][0] & $NicDataArray[$i][1])) {
			# found country
			print "$TargetAddrStr $NicDataArray[$i][2]\n";
			$prevpos=$i;
			last;
		}
	}
	for (local $i=0;$i<$prevpos;$i++) {
		if (($TargetAddr & $NicDataArray[$i][1]) == ($NicDataArray[$i][0] & $NicDataArray[$i][1])) {
			# found country
			print "$TargetAddrStr $NicDataArray[$i][2]\n";
			$prevpos=$i;
			last;
		}
	}
}
