#!/usr/bin/perl
# This script will convert GeoIP data obtained from maxmind
# to Google Earth's KML format for easy viewing. It also 
# supports filters to narrow down interesting areas.
#
# You can get latest GeoIP database here:
# http://www.maxmind.com/app/geolitecity
# Note: Click on "Download the latest GeoLite City CSV Format" link
#       and extract all files in the same directory as this executable
#       The first time you execute, you will have to generate a new
#       database optimized for operations of this script.
#
# Usage:
# geoip2kml -o output_file -f countrycode_or_city_filter
#
# Author: iphelix

use Socket;
use Getopt::Std;

my %options;
getopt('of',\%options);
my $output_file = $options{o};
my $filter = $options{f};

unless (defined $output_file) {
		
print "geoip2kml - Converts GeoIP information to Google Earth's KML format\n";
print "Typical Usage: ./geoip2kml.pl -o filename.kml -f US\n"; 
print " -o filename - specify output KML file name to use with Google Earth\n";
print " -f filter - specify filter (regex) to use when generating KML file\n";
print "             e.g -f US will only show US locations or\n";
print "		    -f \"San Francisco\" will display all ips assigned\n";
print "		    to San Francisco and South San Francisco area\n";
exit 1;
}

###############################################################################
# A) First check if GeoLiteCity-Ids exists which is important for future steps
unless(-e "GeoLiteCity-Ids.csv") {
	print "[!!!] I did not detect GeoLiteCity-Ids.csv file!\n";
	print "[ * ] Generating GeoLiteCity-Ids.csv (it might take awhile)...\n";

	my $temp;
	open(BLOC,"GeoLiteCity-Blocks.csv") || die "[!!!] Couldn't open GeoLiteCity-Blocks.csv\n";

	# skip first two lines
	#read BLOC, $temp, 1; read BLOC, $temp, 1;

	my @ids;
	my ($startIpNum,$endIpNum,$locId);

	my $counter=-1;
	while(<BLOC>) {
		$counter++;
		if($counter < 2) { next; } # skip first two lines
		s/\"//g;
		($startIpNum,$endIpNum,$locId) = split(/,/);
		$ids[$locId] = $ids[$locId].$startIpNum.'-'.$endIpNum."|";	
	}
	close BLOC;

	my $counter=0;

	print "[ * ] Done loading, now sorting ".@ids." elements\n";

	open(IDS,">GeoLiteCity-Ids.csv") || die "[!!!] Couldn't open GeoLiteCity-Ids.csv\n";

	foreach (@ids) {
		print IDS $counter.','.$_."\n";
		unless($counter % 100000) { print "$counter - $_\n"; }
		$counter++;
	}
	close IDS;

}


###############################################################################
# B) Load GeoLiteCity-Ids file and split values into array
my $counter = 0;
my @blocks;

open(BLOC, "GeoLiteCity-Ids.csv") || die "[!!!] Couldn't open GeoLiteCity-Ids.csv\n";
print "[ * ] Loading GeoLiteCity database...\n";
while(<BLOC>) {
	my ($blocId,$ips) = split(/,/);
	$blocks[$blocId] = $ips;
	$counter++;
}
close BLOC;
print "[ * ] Finished loading ".@blocks." elements\n";

###############################################################################
# C) Start generating KML file
print "[ * ] Generating KML file...\n";
$counter = 0;
open(OUT, ">$output_file");

print OUT "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
print OUT "<kml xmlns=\"http://earth.google.com/kml/2.1\">\n";
print OUT "<Document>\n";
print OUT "<Style id='small'>\n";
print OUT "	<IconStyle>\n";
print OUT "		<scale>0.5</scale>\n";
print OUT "		<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>\n";
print OUT "	</IconStyle>\n";
print OUT "</Style>\n";
print OUT "<Style id='medium'>\n";
print OUT "	<IconStyle>\n";
print OUT "		<scale>1.0</scale>\n";
print OUT "		<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>\n";
print OUT "	</IconStyle>\n";
print OUT "</Style>\n";
print OUT "<Style id='large'>\n";
print OUT "	<IconStyle>\n";
print OUT "		<scale>1.5</scale>\n";
print OUT "		<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>\n";
print OUT "	</IconStyle>\n";
print OUT "</Style>\n";
print OUT "<Style id='gigantic'>\n";
print OUT "	<IconStyle>\n";
print OUT "		<scale>2.0</scale>\n";
print OUT "		<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href></Icon>\n";
print OUT "	</IconStyle>\n";
print OUT "</Style>\n";
print OUT "<Folder>\n";
print OUT "	<name>GeoIP $filter</name>\n";

# Go through GeoLiteCity-Location file printing out data
open(LOC, "GeoLiteCity-Location.csv") || die "[!!!] Couldn't open GeoLiteCity-Location.csv\n";
while(<LOC>) {
	s/\"//g;
	my ($locId,$country,$region,$city,$postalCode,$latitude,$longitude,$dmaCode,$areaCode) = split(/,/);
	# skip headers and filtered locations
	if($city eq 'city' || $city eq '') { next; }
	if(defined $filter && ($country ne $filter) && ($city !~ $filter)) { next; }

	my (@ranges) = split(/\|/,$blocks[$locId]);
	pop @ranges; #remove last empty | to avoid 0.0.0.0-0.0.0.0

	my $description = "<![CDATA[\n";
	foreach (@ranges) {
		my ($startIpNum, $endIpNum) = split(/-/);
		chomp($startIpNum);
		$description .= inet_ntoa(pack('N',$startIpNum))."
-".inet_ntoa(pack('N',$endIpNum))."<br>\n";
	}
	$description .= "]]>\n";

	# Add proper styling to the icon depending on the number of IPs in the area
	my $size_style = "small";
	if($#ranges < 10) { $size_style = "small"; }
	elsif($#ranges < 100) { $size_style = "medium"; }
	elsif($#ranges < 1000) { $size_style = "large"; }
	else { $size_style = "gigantic"; }

	print OUT "	<Placemark>\n";
	print OUT "		<name>$city, $country</name>\n";
	print OUT "		<description>".$description."</description>\n";
	print OUT "		<styleUrl>#$size_style</styleUrl>\n";
	print OUT "		<Point>\n";
	print OUT "			<coordinates>$longitude,$latitude</coordinates>\n";
	print OUT "		</Point>\n";
	print OUT "	</Placemark>\n";

	$counter++;
}
close LOC;

print OUT "	</Folder>\n";
print OUT "</Document>\n";
print OUT "</kml>";

print "[ * ] Generated $counter listings\n";	
