# CountryFilter.py
#
# Copyright (C) 2005 Guwashi <guwashi[AT]fooos[DOT]com>
#
# This library 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.1 of the License, or (at your option) any later version.
#
# This library 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 library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# Description:
#
#   The feature of CountryFilter is as follows.
#
#     - This script can display country name by finding the country
#       from IP-address of the player.
#
#     - This script provides access control (allow/deny) based on
#       player country name.
#
#     - This script uses GeoIP Free Country, and its accuracy of
#       conversion is approximately 97%.
#
#   If you want to see latest information of this script, please
#   acccess this URL.
#   http://d3.jpn.org/
#
#
# Download:
#
#   http://d3.jpn.org/bf2/CountryFilter-bf2-1.0.0.zip
#   http://www.maxmind.com/download/geoip/database/GeoIP.dat.gz
#
#
# Install:
#
#   - Copy CountryFilter.py and PurePythonGeoIP.py to
#     '<bf2>/Admin/standard_admin' directory.
#
#   - Add the lines 'import CountryFilter' and 'CountryFilter.init()'
#     to the file '<bf2>/admin/standard_admin/__init__.py'.
#
#     ex.)
#       import autobalance
#       import tk_punish
#       import CountryFilter
#
#       autobalance.init()
#       tk_punish.init()	
#       CountryFilter.init()
#
#   - Download GeoIP.dat from
#     http://www.maxmind.com/download/geoip/database/GeoIP.dat.gz
#     and extract it.
#
#   - Copy GeoIP.dat to '<bf2>' directory.
#
#   - Finally, it must be the following.
#
#     ex.)
#       <bf2>/Admin/standard_admin/CountryFilter.py
#       <bf2>/Admin/standard_admin/PurePythonGeoIP.py
#       <bf2>/GeoIP.dat
#
#
# Config:
#
#   see also
#   http://d3.jpn.org/hl2/CountryFilter-en.html (English)
#   http://d3.jpn.org/hl2/CountryFilter-jp.html (Japanese)
#
#   - You can customize the print format of country.
#
#     - Country code by two characters.  ex.) JP
#
cf_country_print_mode = 'code'
#
#     - Country code by three characters.  ex.) JPN
#
#cf_country_print_mode = 'code3'
#
#     - Long country name.  ex.) Japan
#
#cf_country_print_mode = 'name'
#
#   - You can customize the message format when script allow/deny the
#     connection according to the country of the player.
#
#     - Message format when player was allowed to connect.
#       %(name)s is replaced with player name.
#       %(country)s is replaced with country name.
#       The message will not displayed when setting as
#       cf_allow_message = ''.
#
cf_allow_message = '%(name)s (Country: %(country)s) was allowed to connect.'
#
#     - Message format when player was denied to connect.
#       %(name)s is replaced with player name.
#       %(country)s is replaced with country name.
#       The message will not displayed when setting as
#       cf_deny_message = ''.
#
cf_deny_message =  '%(name)s (Country: %(country)s) was rejected by CountryFilter.'
#
#     - Specify the country(s) which is excluded from printing
#       messages.
#       Available value is same as cf_allow_from and cf_deny_from.
#
cf_message_exclude_from = ''
#
#   - Allowing/denying the connection according to the country of the
#     player.
#
#     You can limit the connection by using cf_allow_from,
#     cf_deny_from, and cf_order. Please specify this by the country
#     code of two characters. Basically, it is the same as the
#     mod_access module of Apache. See this URL.
#
#     http://httpd.apache.org/docs/mod/mod_access.html
#
#     - cf_allow_from
#
#       cf_allow_from specify the country(s) which is allowed to
#       connect. Available value of cf_allow_from is 'all', '', '--'
#       and country code by 2 characters. If you want to specify
#       multiple value, use space as delimiter. Set 'all', if you want
#       to allow all countries. When '' (null string) is specified,
#       nothing is done. '--' is unknown country. Note: when you
#       connect to server from LAN, its IP-address is 192.168.xxx.xxx,
#       so this script cannot find valid country name. Therefore if
#       you want to allow to connect from LAN, you must specify '--'.
#
#       ex.1)
#         # Allowing all countries.
#         cf_allow_from = 'all'
#
#       ex.2)
#         # Allowing only JP, US and LAN.
#         cf_allow_from = 'JP US --'
#
#       ex.3)
#         # Doing nothing.
#         cf_allow_from = ''
#
#     - cf_deny_from
#
#       cf_deny_from specify the country(s) which is deny to
#       connect. Available value of cf_deny_from is quite the same as
#       cf_allow_from.
#
#     - cf_order
#
#       cf_order controls the default access state and the order in
#       which cf_allow_from and cf_deny_from are evaluated. The value
#       of cf_order must be "deny,allow" or "allow,deny". The default
#       value is "deny,allow".
#
#       ex.1)
#         # It is evaluated in order of deny and allow.
#         # Access is allowed by default.
#         cf_order = 'deny,allow'
#
#       ex.2)
#         # It is evaluated in order of allow and deny.
#         # Access is denied by default.
#         cf_order = 'allow,deny'
#
#     - Examples
#
#       Some typical usages are shown as follows.
#
#       - Allowing all countries. (default)
#
cf_order = 'deny,allow'
cf_deny_from = ''
cf_allow_from = 'all'
#
#       - Allowing only JP, US and LAN. Others are denied.
#
#cf_order = 'deny,allow'
#cf_deny_from = 'all'
#cf_allow_from = 'JP US --'
#
#       - Denying only JP. Others are allowed.
#
#cf_order = 'allow,deny'
#cf_allow_from = 'all'
#cf_deny_from = 'JP'
#
#       If you want to find country code, refer this page.
#       http://www.maxmind.com/app/iso3166
#
#   - Specify the path of GeoIP.dat file. Default path is
#     <bf2>/GeoIP.dat.
#
cf_geoipdat_path = 'GeoIP.dat'
#
#
# etc.:
#
#   - The CountryFilter is licensed under the GPL. For details see the
#     COPYING file.
#
#   - This product includes GeoIP data created by MaxMind, available
#     from http://maxmind.com/
#
#
# Author:
#
#   Guwashi
#     - E-Mail
#       guwashi[AT]fooos[DOT]com
#     - Web
#       http://d3.jpn.org/
#     - Forums
#       not yet.
#
# ChangeLog:
#
#   - 1.0.0 2005/07/07
#     first public release.
#   - 1.0.0rc1 2005/07/06
#     added cf_country_print_mode, cf_message_exclude_from and
#     cf_geoipdat_path.
#   - 1.0.0b 2005/07/04
#     first beta.

import sys
import re
import host
import bf2
import PurePythonGeoIP
from PurePythonGeoIP import GeoIP

COUNTRYFILTER_VERSION = '1.0.0'

gi = None

def init():
   '''Create a new GeoIP object and register callback functions.'''
   global gi, cf_geoipdat_path
   printLog('init', 'start...')
   gi = GeoIP.open(cf_geoipdat_path, GeoIP.GEOIP_STANDARD)
   printLog('init', 'testing: 133.1.1.1 ==> ' + gi.country_code_by_addr('133.1.1.1'))
   host.registerHandler('PlayerConnect', onPlayerConnect, 1)
   printLog('init', 'done')

def onPlayerConnect(player):
   '''Examine players country and allow/deny to connect.'''
   global cf_allow_message, cf_deny_message
   printLog('onPlayerConnect', 'start...')
   printLog('onPlayerConnect', 'player.index == ' + str(player.index))
   printLog('onPlayerConnect', 'player.getName() == ' + str(player.getName()))
   printLog('onPlayerConnect', 'player.getAddress() == ' + str(player.getAddress()))
   countryId = gi.id_by_addr(str(player.getAddress()))
   countryCode = GeoIP.id_to_country_code(countryId)
   country = idToCountry(countryId)
   printLog('onPlayerConnect', 'country == ' + country)
   if isAllowConnect(countryCode):
      if 0 < len(cf_allow_message) and (not isMessageExcludeFrom(countryCode)):
         message = cf_allow_message % {'name':player.getName(), 'country':country}
         printConsole(message)
         sayAll(message)
      pass # do nothing
   else:
      if 0 < len(cf_deny_message) and (not isMessageExcludeFrom(countryCode)):
         message = cf_deny_message % {'name':player.getName(), 'country':country}
         printConsole(message)
         sayAll(message)
      host.rcon_invoke('admin.kickPlayer ' + str(player.index)) # kick!
   printLog('onPlayerConnect', 'done')

def printLog(method_name, message):
   '''Print log to stdout.'''
   if bf2.g_debug:
      print '[CF]: ' + str(method_name) + ': ' + str(message)
      sys.stdout.flush()

def printConsole(message):
   '''Print log to server console.'''
   host.rcon_invoke('echo "' + str(message) + '"')

def sayAll(message):
   '''Say message to to all players.'''
   host.rcon_invoke('game.sayAll "' + str(message) + '"')

def isAllowConnect(countryCode):
   '''Is player allowed to connect?'''
   # http://httpd.apache.org/docs/mod/mod_access.html
   # TODO: use regular expressions
   global cf_order, cf_allow_from, cf_deny_from
   result = True
   if 'allow,deny' == cf_order:
      result = False # deny
      if -1 != cf_allow_from.find('all'):
         result = True
      if -1 != cf_allow_from.find(countryCode):
         result = True
      if -1 != cf_deny_from.find('all'):
         result = False
      if -1 != cf_deny_from.find(countryCode):
         result = False
   else: # 'deny,allow' (default)
      result = True; # allow
      if -1 != cf_deny_from.find('all'):
         result = False
      if -1 != cf_deny_from.find(countryCode):
         result = False
      if -1 != cf_allow_from.find('all'):
         result = True
      if -1 != cf_allow_from.find(countryCode):
         result = True
   return result;

def isMessageExcludeFrom(countryCode):
   '''Is message allowed to print?'''
   global cf_message_exclude_from
   result = False
   if -1 != cf_message_exclude_from.find('all'):
      result = True
   if -1 != cf_message_exclude_from.find(countryCode):
      result = True
   return result

def idToCountry(countryId):
   '''Convert country id to country representation.'''
   global cf_country_print_mode
   if 'code3' == cf_country_print_mode:
      return GeoIP.id_to_country_code3(countryId)
   elif 'name' == cf_country_print_mode:
      return GeoIP.id_to_country_name(countryId)
   else: # 'code' (default)
      return GeoIP.id_to_country_code(countryId)