1#!/usr/local/bin/perl 2#----------------------------------------------------------------------------- 3# GeoIp2 Maxmind AWStats plugin 4# This plugin allow you to get country report with countries detected 5# from a Geographical database (GeoIP2 internal database) instead of domain 6# hostname suffix. 7# Need the country database from Maxmind (free). 8#----------------------------------------------------------------------------- 9# Perl Required Module: GeoIP2::Database::Reader 10#----------------------------------------------------------------------------- 11 12 13# <----- 14# ENTER HERE THE USE COMMAND FOR ALL REQUIRED PERL MODULES 15use vars qw/ $type /; 16$type='geoip2_country'; 17if (!eval ('require "GeoIP2/Database/Reader.pm";')) { 18 $error=$@; 19 $ret=($error)?"Error:\n$error":""; 20 $ret.="Error: Need Perl module GeoIP2::Database::Reader"; 21 return $ret; 22} 23# GeoIP2 Perl API doesn't have a ByName lookup so we need to do the resolution ourselves 24if (!eval ('require "Socket.pm";')) { 25 $error=$@; 26 $ret=($error)?"Error:\n$error":""; 27 $ret.="Error: Need Perl module Socket"; 28 return $ret; 29} 30# -----> 31#use strict; 32no strict "refs"; 33 34 35 36#----------------------------------------------------------------------------- 37# PLUGIN VARIABLES 38#----------------------------------------------------------------------------- 39# <----- 40# ENTER HERE THE MINIMUM AWSTATS VERSION REQUIRED BY YOUR PLUGIN 41# AND THE NAME OF ALL FUNCTIONS THE PLUGIN MANAGE. 42my $PluginNeedAWStatsVersion="5.4"; 43my $PluginHooksFunctions="GetCountryCodeByAddr GetCountryCodeByName ShowInfoHost"; 44my $PluginName = "geoip2_country"; 45my $LoadedOverride=0; 46my $OverrideFile=""; 47my %TmpDomainLookup; 48# -----> 49 50# <----- 51# IF YOUR PLUGIN NEED GLOBAL VARIABLES, THEY MUST BE DECLARED HERE. 52use vars qw/ 53$reader 54/; 55use Data::Validate::IP 0.25 qw( is_public_ip ); 56# -----> 57 58 59#----------------------------------------------------------------------------- 60# PLUGIN FUNCTION: Init_pluginname 61#----------------------------------------------------------------------------- 62sub Init_geoip2_country { 63 my $InitParams=shift; 64 my $checkversion=&Check_Plugin_Version($PluginNeedAWStatsVersion); 65 66 # <----- 67 # ENTER HERE CODE TO DO INIT PLUGIN ACTIONS 68 debug(" Plugin $PluginName: InitParams=$InitParams",1); 69 my ($datafile,$override)=split(/\+/,$InitParams,2); 70 if (! $datafile) { $datafile="GeoLite2-Country.mmdb"; } 71 else { $datafile =~ s/%20/ /g; } 72 if ($override){$OverrideFile=$override;} 73 %TmpDomainLookup=(); 74 debug(" Plugin $PluginName: GeoIP2 try to initialize override=$override datafile=$datafile",1); 75 $reader = GeoIP2::Database::Reader->new( 76 file => $datafile, 77 locales => [ 'en', 'de', ] 78 ); 79 80 # Fails on some GeoIP version 81 # debug(" Plugin $PluginName: GeoIP initialized database_info=".$reader->database_info()); 82 if ($reader) { debug(" Plugin $PluginName: GeoIP2 plugin and reader object initialized",1); } 83 else { return "Error: Failed to create reader object for datafile=".$datafile; } 84 # -----> 85 86 return ($checkversion?$checkversion:"$PluginHooksFunctions"); 87} 88 89 90#----------------------------------------------------------------------------- 91# PLUGIN FUNCTION: GetCountryCodeByAddr_pluginname 92# UNIQUE: YES (Only one plugin using this function can be loaded) 93# GetCountryCodeByAddr is called to translate an ip into a country code in lower case. 94#----------------------------------------------------------------------------- 95sub GetCountryCodeByAddr_geoip2_country { 96 my $param = shift; 97 if (! $param) { return ''; } 98 my $res = Lookup_geoip2_country($param); 99 return ($res) ? lc($res) : 'unknown'; 100} 101 102 103#----------------------------------------------------------------------------- 104# PLUGIN FUNCTION: GetCountryCodeByName_pluginname 105# UNIQUE: YES (Only one plugin using this function can be loaded) 106# GetCountryCodeByName is called to translate a host name into a country code in lower case. 107#----------------------------------------------------------------------------- 108sub GetCountryCodeByName_geoip2_country { 109 return GetCountryCodeByAddr_geoip2_country(@_); 110} 111 112 113#----------------------------------------------------------------------------- 114# PLUGIN FUNCTION: ShowInfoHost_pluginname 115# UNIQUE: NO (Several plugins using this function can be loaded) 116# Function called to add additionnal columns to the Hosts report. 117# This function is called when building rows of the report (One call for each 118# row). So it allows you to add a column in report, for example with code : 119# print "<TD>This is a new cell for $param</TD>"; 120# Parameters: Host name or ip 121#----------------------------------------------------------------------------- 122sub ShowInfoHost_geoip2_country { 123 my $param="$_[0]"; 124 # <----- 125 if ($param eq '__title__') { 126 my $NewLinkParams=${QueryString}; 127 $NewLinkParams =~ s/(^|&)update(=\w*|$)//i; 128 $NewLinkParams =~ s/(^|&)output(=\w*|$)//i; 129 $NewLinkParams =~ s/(^|&)staticlinks(=\w*|$)//i; 130 $NewLinkParams =~ s/(^|&)framename=[^&]*//i; 131 my $NewLinkTarget=''; 132 if ($DetailedReportsOnNewWindows) { $NewLinkTarget=" target=\"awstatsbis\""; } 133 if (($FrameName eq 'mainleft' || $FrameName eq 'mainright') && $DetailedReportsOnNewWindows < 2) { 134 $NewLinkParams.="&framename=mainright"; 135 $NewLinkTarget=" target=\"mainright\""; 136 } 137 $NewLinkParams =~ tr/&/&/s; $NewLinkParams =~ s/^&//; $NewLinkParams =~ s/&$//; 138 if ($NewLinkParams) { $NewLinkParams="${NewLinkParams}&"; } 139 140 print "<th width=\"80\">"; 141 print "<a href=\"#countries\">GeoIP2<br />Country</a>"; 142 print "</th>"; 143 } 144 elsif ($param) { 145 my $res = Lookup_geoip2_country($param); 146 if ($res) { 147 $res = lc($res); 148 print $DomainsHashIDLib{$res}?$DomainsHashIDLib{$res}:"<span style=\"color: #$color_other\">$Message[0]</span>"; 149 } 150 else { print "<span style=\"color: #$color_other\">$Message[0]</span>"; } 151 print "</td>"; 152 } 153 else { 154 print "<td> </td>"; 155 } 156 return 1; 157 # -----> 158} 159 160#----------------------------------------------------------------------------- 161# PLUGIN FUNCTION: LoadOverrideFile 162# Attempts to load a comma delimited file that will override the GeoIP database 163# Useful for Intranet records 164# CSV format: IP,2-char Country code 165#----------------------------------------------------------------------------- 166sub LoadOverrideFile_geoip2_country{ 167 my $filetoload=""; 168 if ($OverrideFile){ 169 if (!open(GEOIPFILE, $OverrideFile)){ 170 debug("Plugin $PluginName: Unable to open override file: $OverrideFile"); 171 $LoadedOverride = 1; 172 return; 173 } 174 }else{ 175 my $conf = (exists(&Get_Config_Name) ? Get_Config_Name() : $SiteConfig); 176 if ($conf && open(GEOIPFILE,"$DirData/$PluginName.$conf.txt")) { $filetoload="$DirData/$PluginName.$conf.txt"; } 177 elsif (open(GEOIPFILE,"$DirData/$PluginName.txt")) { $filetoload="$DirData/$PluginName.txt"; } 178 else { debug("No override file \"$DirData/$PluginName.txt\": $!"); } 179 } 180 if ($filetoload) 181 { 182 # This is the fastest way to load with regexp that I know 183 while (<GEOIPFILE>){ 184 chomp $_; 185 s/\r//; 186 my @record = split(",", $_); 187 # replace quotes if they were used in the file 188 foreach (@record){ $_ =~ s/"//g; } 189 # store in hash 190 $TmpDomainLookup{$record[0]} = $record[1]; 191 } 192 close GEOIPFILE; 193 debug(" Plugin $PluginName: Overload file loaded: ".(scalar keys %TmpDomainLookup)." entries found."); 194 } 195 $LoadedOverride = 1; 196 return; 197} 198 199#----------------------------------------------------------------------------- 200# PLUGIN FUNCTION: Lookup 201# Looks up the input parameter (either ip address or dns name) and returns its 202# associated country code; or undefined if not available. 203# GEOIP entry 204#----------------------------------------------------------------------------- 205sub Lookup_geoip2_country { 206 $param = shift; 207 if (!$LoadedOverride) { &LoadOverrideFile_geoip2_country(); } 208 if ($Debug) { debug(" Plugin $PluginName: Lookup_geoip2_country for $param",5); } 209 if ($reader && !exists($TmpDomainLookup{$param})) { 210 $TmpDomainLookup{$param} = undef; # negative entry to avoid repeated lookups 211 # Resolve the parameter (either a name or an ip address) to a list of network addresses 212 my ($err, @result) = Socket::getaddrinfo($param, undef, { protocol => Socket::IPPROTO_TCP, socktype => Socket::SOCK_STREAM }); 213 for (@result) { 214 # Convert the network address to human-readable form 215 my ($err, $address, $servicename) = Socket::getnameinfo($_->{addr}, Socket::NI_NUMERICHOST, Socket::NIx_NOSERV); 216 next if ($err || !is_public_ip($address)); 217 218 if ($Debug && $param ne $address) { debug(" Plugin $PluginName: Lookup_geoip2_country $param resolved to $address",5); } 219 eval { 220 my $record = $reader->country(ip => $address); 221 $TmpDomainLookup{$param} = $record->country()->iso_code(); 222 last; 223 } 224 } 225 } 226 my $res = $TmpDomainLookup{$param}; 227 if ($Debug) { debug(" Plugin $PluginName: Lookup_geoip2_country for $param: [$res]",5); } 228 return $res; 229} 230 2311; # Do not remove this line 232