]> git.ipfire.org Git - thirdparty/AWStats.git/commitdiff
GeoIP2 Support Plugin
authorNeil Gierman <neilgierman@users.noreply.github.com>
Tue, 17 Apr 2018 21:10:53 +0000 (16:10 -0500)
committerNeil Gierman <neilgierman@users.noreply.github.com>
Tue, 17 Apr 2018 21:10:53 +0000 (16:10 -0500)
Initial implementation. Looksup only Country code for IPv4 and IPv6

wwwroot/cgi-bin/awstats.model.conf
wwwroot/cgi-bin/awstats.pl
wwwroot/cgi-bin/plugins/geoip2.pm [new file with mode: 0644]

index 6b0f430fe02d8140f404aa2931b0469b9068c4e6..2112cd8c85cbc16fde1503c35e020c4a3d8951ac 100644 (file)
@@ -180,8 +180,8 @@ HostAliases="localhost 127.0.0.1 REGEX[myserver\.com$]"
 # dramatically AWStats update process speed. Do not use on large web sites.
 # Note: Reverse DNS lookup is done on IPv4 only (Enable ipv6 plugin for IPv6).
 # Note: Result of DNS Lookup can be used to build the Country report. However
-# it is highly recommanded to enable the plugin 'geoip' or 'geoipfree' to
-# have an accurate Country report with no need of DNS Lookup.
+# it is highly recommanded to enable the plugin 'geoip', 'geoipfree', or 'geoip2'
+# to have an accurate Country report with no need of DNS Lookup.
 # Possible values:
 # 0 - No DNS Lookup
 # 1 - DNS Lookup is fully enabled
@@ -1459,6 +1459,16 @@ color_x="C1B2E2"                         # Background color for number of exit pages (Default = "C1B2
 #
 #LoadPlugin="geoip GEOIP_STANDARD /pathto/GeoIP.dat"
 
+# PLUGIN: GeoIP2
+# REQUIRED MODULES: GeoIP2::Database::Reader (from Maxmind)
+# PARAMETERS: [/pathto/GeoLite2-Country.mmdb[+/pathto/override.txt]]
+# DESCRIPTION: Builds a country chart and adds an entry to the hosts
+# table with country name. This uses the new schema of GeoIP2 replacing
+# the now expired Legacy schema.
+# Replace spaces in the path of geoip data file with string "%20".
+#
+#LoadPlugin="geoip2 /pathto/GeoLite2-Country.mmdb"
+
 # PLUGIN: GeoIP6
 # REQUIRED MODULES: Geo::IP or Geo::IP::PurePerl (from Maxmind, version >= 1.40)
 # PARAMETERS: [GEOIP_STANDARD | GEOIP_MEMORY_CACHE] [/pathto/geoipv6.dat[+/pathto/override.txt]]
index b04ed6f8fe931d36b4e17a9c2f2ade504033b8f5..dc08a48bc4a59bd440088021e68c7ff3c0ff9600 100755 (executable)
@@ -3169,6 +3169,7 @@ sub Read_Plugins {
                                        'geoipfree'            => 'u',
                                        'geoip'                => 'ou',
                                        'geoip6'               => 'ou',
+                                       'geoip2'               => 'ou',
                                        'geoip_region_maxmind' => 'mou',
                                        'geoip_city_maxmind'   => 'mou',
                                        'geoip_isp_maxmind'    => 'mou',
@@ -3345,7 +3346,8 @@ sub Read_Plugins {
 # In output mode, geo ip plugins are not loaded, so message changes are done here (can't be done in plugin init function)
        if (   $PluginsLoaded{'init'}{'geoip'}
                || $PluginsLoaded{'init'}{'geoip6'}
-               || $PluginsLoaded{'init'}{'geoipfree'} )
+               || $PluginsLoaded{'init'}{'geoipfree'}
+               || $PluginsLoaded{'init'}{'geoip2'})
        {
                $Message[17] = $Message[25] = $Message[148];
        }
@@ -19404,6 +19406,9 @@ if ( $UpdateStats && $FrameName ne 'index' && $FrameName ne 'mainleft' )
                                elsif ( $PluginsLoaded{'GetCountryCodeByAddr'}{'geoipfree'} ) {
                                        $Domain = GetCountryCodeByAddr_geoipfree($HostResolved);
                                }
+                               elsif ( $PluginsLoaded{'GetCountryCodeByAddr'}{'geoip2'} ) {
+                                       $Domain = GetCountryCodeByAddr_geoip2($HostResolved);
+                               }
                                if ($AtLeastOneSectionPlugin) {
                                        foreach my $pluginname (
                                                keys %{ $PluginsLoaded{'SectionProcessIp'} } )
@@ -19438,6 +19443,11 @@ if ( $UpdateStats && $FrameName ne 'index' && $FrameName ne 'mainleft' )
                                        {
                                                $Domain = GetCountryCodeByAddr_geoipfree($Host);
                                        }
+                                       elsif (
+                                               $PluginsLoaded{'GetCountryCodeByAddr'}{'geoip2'} )
+                                       {
+                                               $Domain = GetCountryCodeByAddr_geoip2($Host);
+                                       }
                                        elsif ( $HostResolved =~ /\.(\w+)$/ ) { $Domain = $1; }
                                        if ($AtLeastOneSectionPlugin) {
                                                foreach my $pluginname (
@@ -19467,6 +19477,11 @@ if ( $UpdateStats && $FrameName ne 'index' && $FrameName ne 'mainleft' )
                                        {
                                                $Domain = GetCountryCodeByName_geoipfree($HostResolved);
                                        }
+                                       elsif (
+                                               $PluginsLoaded{'GetCountryCodeByName'}{'geoip2'} )
+                                       {
+                                               $Domain = GetCountryCodeByName_geoip2($HostResolved);
+                                       }
                                        elsif ( $HostResolved =~ /\.(\w+)$/ ) { $Domain = $1; }
                                        if ($AtLeastOneSectionPlugin) {
                                                foreach my $pluginname (
diff --git a/wwwroot/cgi-bin/plugins/geoip2.pm b/wwwroot/cgi-bin/plugins/geoip2.pm
new file mode 100644 (file)
index 0000000..6e644a3
--- /dev/null
@@ -0,0 +1,272 @@
+#!/usr/bin/perl
+#-----------------------------------------------------------------------------
+# GeoIp2 Maxmind AWStats plugin
+# This plugin allow you to get country report with countries detected
+# from a Geographical database (GeoIP2 internal database) instead of domain
+# hostname suffix.
+# Need the country database from Maxmind (free).
+#-----------------------------------------------------------------------------
+# Perl Required Module: GeoIP2::Database::Reader
+#-----------------------------------------------------------------------------
+
+
+# <-----
+# ENTER HERE THE USE COMMAND FOR ALL REQUIRED PERL MODULES
+use vars qw/ $type /;
+$type='geoip2';
+if (!eval ('require "GeoIP2/Database/Reader.pm";')) {
+       $error=$@;
+    $ret=($error)?"Error:\n$error":"";
+    $ret.="Error: Need Perl module GeoIP2::Database::Reader";
+    return $ret;
+}
+# GeoIP2 Perl API doesn't have a ByName lookup so we need to do the resolution ourselves
+if (!eval ('require "Socket.pm";')) {
+       $error=$@;
+    $ret=($error)?"Error:\n$error":"";
+    $ret.="Error: Need Perl module Socket";
+    return $ret;
+}
+# ----->
+#use strict;
+no strict "refs";
+
+
+
+#-----------------------------------------------------------------------------
+# PLUGIN VARIABLES
+#-----------------------------------------------------------------------------
+# <-----
+# ENTER HERE THE MINIMUM AWSTATS VERSION REQUIRED BY YOUR PLUGIN
+# AND THE NAME OF ALL FUNCTIONS THE PLUGIN MANAGE.
+my $PluginNeedAWStatsVersion="5.4";
+my $PluginHooksFunctions="GetCountryCodeByAddr GetCountryCodeByName ShowInfoHost";
+my $PluginName = "geoip2";
+my $LoadedOverride=0;
+my $OverrideFile="";
+my %TmpDomainLookup;
+# ----->
+
+# <-----
+# IF YOUR PLUGIN NEED GLOBAL VARIABLES, THEY MUST BE DECLARED HERE.
+use vars qw/
+$reader
+/;
+# ----->
+
+
+#-----------------------------------------------------------------------------
+# PLUGIN FUNCTION: Init_pluginname
+#-----------------------------------------------------------------------------
+sub Init_geoip2 {
+       my $InitParams=shift;
+       my $checkversion=&Check_Plugin_Version($PluginNeedAWStatsVersion);
+
+       # <-----
+       # ENTER HERE CODE TO DO INIT PLUGIN ACTIONS
+       debug(" Plugin $PluginName: InitParams=$InitParams",1);
+       my $datafile=$InitParams;
+       if (! $datafile) { $datafile="$PluginName.dat"; }
+    else { $datafile =~ s/%20/ /g; }
+       # if ($type eq 'geoippureperl') {
+               # if ($mode eq '' || $mode eq 'GEOIP_MEMORY_CACHE')  { $mode=Geo::IP::PurePerl::GEOIP_MEMORY_CACHE(); }
+               # else { $mode=Geo::IP::PurePerl::GEOIP_STANDARD(); }
+       # } else {
+               # if ($mode eq '' || $mode eq 'GEOIP_MEMORY_CACHE')  { $mode=Geo::IP::GEOIP_MEMORY_CACHE(); }
+               # else { $mode=Geo::IP::GEOIP_STANDARD(); }
+       # }
+       if ($override){$OverrideFile=$override;}
+       %TmpDomainLookup=();
+       debug(" Plugin $PluginName: GeoIP2 try to initialize type=$type mode=$mode override=$override datafile=$datafile",1);
+       $reader = GeoIP2::Database::Reader->new(
+        file    => $datafile,
+        locales => [ 'en', 'de', ]
+    );
+
+       # Fails on some GeoIP version
+       # debug(" Plugin $PluginName: GeoIP initialized database_info=".$reader->database_info());
+       if ($reader) { debug(" Plugin $PluginName: GeoIP2 plugin and reader object initialized",1); }
+       else { return "Error: Failed to create reader object for datafile=".$datafile; }
+       # ----->
+
+       return ($checkversion?$checkversion:"$PluginHooksFunctions");
+}
+
+
+#-----------------------------------------------------------------------------
+# PLUGIN FUNCTION: GetCountryCodeByAddr_pluginname
+# UNIQUE: YES (Only one plugin using this function can be loaded)
+# GetCountryCodeByAddr is called to translate an ip into a country code in lower case.
+#-----------------------------------------------------------------------------
+sub GetCountryCodeByAddr_geoip2 {
+    my $param="$_[0]";
+       # <-----
+       if (! $param) { return ''; }
+       my $res= TmpLookup_geoip2($param);
+       if (! $res) {
+               $res=lc($reader->country( ip => $param)->country()->iso_code()) || 'unknown';
+               $TmpDomainLookup{$param}=$res;
+               if ($Debug) { debug("  Plugin $PluginName: GetCountryCodeByAddr for $param: [$res]",5); }
+       }
+       elsif ($Debug) { debug("  Plugin $PluginName: GetCountryCodeByAddr for $param: Already resolved to [$res]",5); }
+       # ----->
+       return $res;
+}
+
+
+#-----------------------------------------------------------------------------
+# PLUGIN FUNCTION: GetCountryCodeByName_pluginname
+# UNIQUE: YES (Only one plugin using this function can be loaded)
+# GetCountryCodeByName is called to translate a host name into a country code in lower case.
+#-----------------------------------------------------------------------------
+sub GetCountryCodeByName_geoip2 {
+    my $param="$_[0]";
+       # <-----
+       if (! $param) { return ''; }
+       my $res = TmpLookup_geoip($param);
+       if (! $res) {
+        # First resolve the name to an IP
+        $address = inet_ntoa(inet_aton($param));
+        # Now do the same lookup from the IP
+               $res=lc($reader->country($address)->country()->iso_code()) || 'unknown';
+               $TmpDomainLookup{$param}=$res;
+               if ($Debug) { debug("  Plugin $PluginName: GetCountryCodeByName for $param: [$res]",5); }
+       }
+       elsif ($Debug) { debug("  Plugin $PluginName: GetCountryCodeByName for $param: Already resolved to [$res]",5); }
+       # ----->
+       return $res;
+}
+
+
+#-----------------------------------------------------------------------------
+# PLUGIN FUNCTION: ShowInfoHost_pluginname
+# UNIQUE: NO (Several plugins using this function can be loaded)
+# Function called to add additionnal columns to the Hosts report.
+# This function is called when building rows of the report (One call for each
+# row). So it allows you to add a column in report, for example with code :
+#   print "<TD>This is a new cell for $param</TD>";
+# Parameters: Host name or ip
+#-----------------------------------------------------------------------------
+sub ShowInfoHost_geoip2 {
+    my $param="$_[0]";
+       # <-----
+       if ($param eq '__title__') {
+       my $NewLinkParams=${QueryString};
+       $NewLinkParams =~ s/(^|&)update(=\w*|$)//i;
+       $NewLinkParams =~ s/(^|&)output(=\w*|$)//i;
+       $NewLinkParams =~ s/(^|&)staticlinks(=\w*|$)//i;
+       $NewLinkParams =~ s/(^|&)framename=[^&]*//i;
+       my $NewLinkTarget='';
+       if ($DetailedReportsOnNewWindows) { $NewLinkTarget=" target=\"awstatsbis\""; }
+       if (($FrameName eq 'mainleft' || $FrameName eq 'mainright') && $DetailedReportsOnNewWindows < 2) {
+               $NewLinkParams.="&framename=mainright";
+               $NewLinkTarget=" target=\"mainright\"";
+       }
+       $NewLinkParams =~ tr/&/&/s; $NewLinkParams =~ s/^&//; $NewLinkParams =~ s/&$//;
+       if ($NewLinkParams) { $NewLinkParams="${NewLinkParams}&"; }
+
+               print "<th width=\"80\">";
+        print "<a href=\"#countries\">GeoIP2<br />Country</a>";
+        print "</th>";
+       }
+       elsif ($param) {
+        my $ip=0;
+               my $key;
+               if ($param =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { # IPv4 address
+                   $ip=4;
+                       $key=$param;
+               }
+               elsif ($param =~ /^[0-9A-F]*:/i) {                                              # IPv6 address
+                   $ip=6;
+                       $key=$param;
+               }
+               print "<td>";
+               if ($key && $ip==4) {
+                       my $res = TmpLookup_geoip2($param);
+               if (!$res){$res=lc($reader->country($param)->country()->iso_code()) if $reader;}
+               if ($Debug) { debug("  Plugin $PluginName: GetCountryByIp for $param: [$res]",5); }
+                   if ($res) { print $DomainsHashIDLib{$res}?$DomainsHashIDLib{$res}:"<span style=\"color: #$color_other\">$Message[0]</span>"; }
+                   else { print "<span style=\"color: #$color_other\">$Message[0]</span>"; }
+               }
+               if ($key && $ip==6) {                              # GeoIP2 supports both IPv4 and IPv6
+                       my $res = TmpLookup_geoip2($param);
+               if (!$res){$res=lc($reader->country($param)->country()->iso_code()) if $reader;}
+               if ($Debug) { debug("  Plugin $PluginName: GetCountryByIp for $param: [$res]",5); }
+                   if ($res) { print $DomainsHashIDLib{$res}?$DomainsHashIDLib{$res}:"<span style=\"color: #$color_other\">$Message[0]</span>"; }
+                   else { print "<span style=\"color: #$color_other\">$Message[0]</span>"; }
+               }
+               if (! $key) {
+                       my $res = TmpLookup_geoip2($param);
+               if (!$res){$res=lc($reader->country($param)->country()->iso_code()) if $reader;}
+               if ($Debug) { debug("  Plugin $PluginName: GetCountryByHostname for $param: [$res]",5); }
+                   if ($res) { print $DomainsHashIDLib{$res}?$DomainsHashIDLib{$res}:"<span style=\"color: #$color_other\">$Message[0]</span>"; }
+                   else { print "<span style=\"color: #$color_other\">$Message[0]</span>"; }
+               }
+               print "</td>";
+       }
+       else {
+               print "<td>&nbsp;</td>";
+       }
+       return 1;
+       # ----->
+}
+
+#-----------------------------------------------------------------------------
+# PLUGIN FUNCTION: LoadOverrideFile
+# Attempts to load a comma delimited file that will override the GeoIP database
+# Useful for Intranet records
+# CSV format: IP,2-char Country code
+#-----------------------------------------------------------------------------
+sub LoadOverrideFile_geoip2{
+       my $filetoload="";
+       if ($OverrideFile){
+               if (!open(GEOIPFILE, $OverrideFile)){
+                       debug("Plugin $PluginName: Unable to open override file: $OverrideFile");
+                       $LoadedOverride = 1;
+                       return;
+               }
+       }else{
+               my $conf = (exists(&Get_Config_Name) ? Get_Config_Name() : $SiteConfig);
+               if ($conf && open(GEOIPFILE,"$DirData/$PluginName.$conf.txt"))  { $filetoload="$DirData/$PluginName.$conf.txt"; }
+               elsif (open(GEOIPFILE,"$DirData/$PluginName.txt"))      { $filetoload="$DirData/$PluginName.txt"; }
+               else { debug("No override file \"$DirData/$PluginName.txt\": $!"); }
+       }
+       if ($filetoload)
+       {
+               # This is the fastest way to load with regexp that I know
+               while (<GEOIPFILE>){
+                       chomp $_;
+                       s/\r//;
+                       my @record = split(",", $_);
+                       # replace quotes if they were used in the file
+                       foreach (@record){ $_ =~ s/"//g; }
+                       # store in hash
+                       $TmpDomainLookup{$record[0]} = $record[1];
+               }
+               close GEOIPFILE;
+        debug(" Plugin $PluginName: Overload file loaded: ".(scalar keys %TmpDomainLookup)." entries found.");
+       }
+       $LoadedOverride = 1;
+       return;
+}
+
+#-----------------------------------------------------------------------------
+# PLUGIN FUNCTION: TmpLookup
+# Searches the temporary hash for the parameter value and returns the corresponding
+# GEOIP entry
+#-----------------------------------------------------------------------------
+sub TmpLookup_geoip2(){
+       $param = shift;
+       if (!$LoadedOverride){&LoadOverrideFile_geoip2();}
+       #my $val;
+       #if ($reader &&
+       #(($type eq 'geoip' && $reader->VERSION >= 1.30) || 
+       #  $type eq 'geoippureperl' && $reader->VERSION >= 1.17)){
+       #       $val = $TmpDomainLookup{$reader->get_ip_address($param)};
+       #}
+    #else {$val = $TmpDomainLookup{$param};}
+    #return $val || '';
+    return $TmpDomainLookup{$param}||'';
+}
+
+1;     # Do not remove this line