Scripts:CountryFilter
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%.
The Code
# 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)
#! /usr/bin/python # PurePythonGeoIP.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 # This code is based on # http://www.maxmind.com/app/python # http://www.maxmind.com/download/geoip/api/pureperl import re import struct PUREPYTHONGEOIP_VERSION = '1.0.0' def nreverse(sequence): '''nreverse in Common Lisp. :)''' sequence.reverse() return sequence class GeoIP: __STANDARD_RECORD_LENGTH = 3 __GEOIP_COUNTRY_EDITION = 106 __GEOIP_COUNTRY_BEGIN = 16776960 __COUNTRIES = ('--','AP','EU','AD','AE','AF','AG','AI','AL','AM','AN','AO','AQ','AR','AS','AT','AU','AW','AZ','BA','BB','BD','BE','BF','BG','BH','BI','BJ','BM','BN','BO','BR','BS','BT','BV','BW','BY','BZ','CA','CC','CD','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CX','CY','CZ','DE','DJ','DK','DM','DO','DZ','EC','EE','EG','EH','ER','ES','ET','FI','FJ','FK','FM','FO','FR','FX','GA','GB','GD','GE','GF','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GW','GY','HK','HM','HN','HR','HT','HU','ID','IE','IL','IN','IO','IQ','IR','IS','IT','JM','JO','JP','KE','KG','KH','KI','KM','KN','KP','KR','KW','KY','KZ','LA','LB','LC','LI','LK','LR','LS','LT','LU','LV','LY','MA','MC','MD','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MX','MY','MZ','NA','NC','NE','NF','NG','NI','NL','NO','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH','PK','PL','PM','PN','PR','PS','PT','PW','PY','QA','RE','RO','RU','RW','SA','SB','SC','SD','SE','SG','SH','SI','SJ','SK','SL','SM','SN','SO','SR','ST','SV','SY','SZ','TC','TD','TF','TG','TH','TJ','TK','TM','TN','TO','TP','TR','TT','TV','TW','TZ','UA','UG','UM','US','UY','UZ','VA','VC','VE','VG','VI','VN','VU','WF','WS','YE','YT','YU','ZA','ZM','ZR','ZW','A1','A2') __CODE3S = ('--','AP','EU','AND','ARE','AFG','ATG','AIA','ALB','ARM','ANT','AGO','AQ','ARG','ASM','AUT','AUS','ABW','AZE','BIH','BRB','BGD','BEL','BFA','BGR','BHR','BDI','BEN','BMU','BRN','BOL','BRA','BHS','BTN','BV','BWA','BLR','BLZ','CAN','CC','COD','CAF','COG','CHE','CIV','COK','CHL','CMR','CHN','COL','CRI','CUB','CPV','CX','CYP','CZE','DEU','DJI','DNK','DMA','DOM','DZA','ECU','EST','EGY','ESH','ERI','ESP','ETH','FIN','FJI','FLK','FSM','FRO','FRA','FX','GAB','GBR','GRD','GEO','GUF','GHA','GIB','GRL','GMB','GIN','GLP','GNQ','GRC','GS','GTM','GUM','GNB','GUY','HKG','HM','HND','HRV','HTI','HUN','IDN','IRL','ISR','IND','IO','IRQ','IRN','ISL','ITA','JAM','JOR','JPN','KEN','KGZ','KHM','KIR','COM','KNA','PRK','KOR','KWT','CYM','KAZ','LAO','LBN','LCA','LIE','LKA','LBR','LSO','LTU','LUX','LVA','LBY','MAR','MCO','MDA','MDG','MHL','MKD','MLI','MMR','MNG','MAC','MNP','MTQ','MRT','MSR','MLT','MUS','MDV','MWI','MEX','MYS','MOZ','NAM','NCL','NER','NFK','NGA','NIC','NLD','NOR','NPL','NRU','NIU','NZL','OMN','PAN','PER','PYF','PNG','PHL','PAK','POL','SPM','PCN','PRI','PSE','PRT','PLW','PRY','QAT','REU','ROM','RUS','RWA','SAU','SLB','SYC','SDN','SWE','SGP','SHN','SVN','SJM','SVK','SLE','SMR','SEN','SOM','SUR','STP','SLV','SYR','SWZ','TCA','TCD','TF','TGO','THA','TJK','TKL','TLS','TKM','TUN','TON','TUR','TTO','TUV','TWN','TZA','UKR','UGA','UM','USA','URY','UZB','VAT','VCT','VEN','VGB','VIR','VNM','VUT','WLF','WSM','YEM','YT','YUG','ZAF','ZMB','ZR','ZWE','A1','A2') __NAMES = ("--","Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles","Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados","Bangladesh","Belgium","Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia","Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the","Central African Republic","Congo","Switzerland","Cote D'Ivoire","Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica","Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic","Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji","Falkland Islands (Malvinas)","Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana","Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala","Guam","Guinea-Bissau","Guyana","Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia","Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan","Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis", "Korea, Democratic People's Republic of","Korea, Republic of","Kuwait","Cayman Islands","Kazakhstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania","Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar","Marshall Islands","Macedonia","Mali","Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives","Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua","Netherlands","Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia","Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory, Occupied","Portugal","Palau","Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia","Solomon Islands","Seychelles","Sudan","Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname","Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo","Thailand","Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","East Timor","Turkey","Trinidad and Tobago","Tuvalu","Taiwan","Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands","United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines","Venezuela","Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Yugoslavia","South Africa","Zambia","Zaire","Zimbabwe", "Anonymous Proxy","Satellite Provider") __RE_IP_DOTTED_FORM = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") GEOIP_STANDARD = 0 # TODO def addr_to_num(ip_address): '''Convert IP-address to number.''' # a = [int(s) for s in re.split('\.', ip_address)] a = map(int, re.split('\.', ip_address)) return a[0] * 16777216L + a[1] * 65536L + a[2] * 256L + a[3] def open(db_file, flags): '''Create a new GeoIP object.''' gi = GeoIP() gi.db_file = db_file gi.flags = flags # TODO gi.fh = open(db_file, 'rb') gi.databaseType = GeoIP.__GEOIP_COUNTRY_EDITION # TODO gi.record_length = GeoIP.__STANDARD_RECORD_LENGTH # TODO gi.databaseSegments = GeoIP.__GEOIP_COUNTRY_BEGIN # TODO return gi def new(flags): '''Create a new GeoIP object.''' return GeoIP.open('/usr/local/share/GeoIP/GeoIP.dat', flags) def __seek_country(self, ipnum): '''Seek GeoIP data file to find country id.''' fh = self.fh record_length = self.record_length databaseSegments = self.databaseSegments offset = 0 x0 = -1 x1 = -1 for depth in nreverse(range(32)): fh.seek(offset * 2 * record_length, 0) x0 = fh.read(record_length) x1 = fh.read(record_length) x0, = struct.unpack("<1l", x0 + "\0") x1, = struct.unpack("<1l", x1 + "\0") if ipnum & (1L << depth): if x1 >= databaseSegments: return x1 offset = x1 else: if x0 >= databaseSegments: return x0 offset = x0 raise Exception('Error Traversing Database for ipnum = %d - Perhaps database is corrupt?' % ipnum) # TODO def id_by_addr(self, ip_address): '''Find country id by IP-address.''' if self.__RE_IP_DOTTED_FORM.match(ip_address): return self.__seek_country(self.addr_to_num(ip_address)) - self.__GEOIP_COUNTRY_BEGIN else: return 0 def id_to_country_code(id): '''Convert country id to country code.''' return GeoIP.__COUNTRIES[id] def id_to_country_code3(id): '''Convert country id to country code.''' return GeoIP.__CODE3S[id] def id_to_country_name(id): '''Convert country id to country code.''' return GeoIP.__NAMES[id] def country_code_by_addr(self, ip_address): '''Find country code by IP-address.''' return GeoIP.id_to_country_code(self.id_by_addr(ip_address)) def country_code3_by_addr(self, ip_address): '''Find country code3 by IP-address.''' return GeoIP.id_to_country_code3(self.id_by_addr(ip_address)) def country_name_by_addr(self, ip_address): '''Find country name by IP-address.''' return GeoIP.id_to_country_name(self.id_by_addr(ip_address)) addr_to_num = staticmethod(addr_to_num) open = staticmethod(open) new = staticmethod(new) id_to_country_code = staticmethod(id_to_country_code) id_to_country_code3 = staticmethod(id_to_country_code3) id_to_country_name = staticmethod(id_to_country_name) if '__main__' == __name__: import sys import string argc = len(sys.argv) gi = None if 2 == argc: gi = GeoIP.new(GeoIP.GEOIP_STANDARD) elif 3 == argc: gi = GeoIP.open(sys.argv[2], GeoIP.GEOIP_STANDARD) else: print "Usage:\n %s <ipaddress> [geoipdatafile]" % sys.argv[0] sys.exit(1) ip = sys.argv[1] id = str(gi.id_by_addr(ip)) code = gi.country_code_by_addr(ip) code3 = gi.country_code3_by_addr(ip) name = gi.country_name_by_addr(ip) print string.join(['ipaddress', 'id', 'code', 'code3', 'name'], "\t") print string.join([ip, id, code, code3, name], "\t")