5e3a189642
Since we ship an ancient version of the database, help server operators keep track of the database version.
430 lines
12 KiB
C++
430 lines
12 KiB
C++
/**
|
|
* vim: set ts=4 :
|
|
* =============================================================================
|
|
* SourceMod GeoIP Extension
|
|
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
|
* =============================================================================
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, version 3.0, as published by the
|
|
* Free Software Foundation.
|
|
*
|
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* As a special exception, AlliedModders LLC gives you permission to link the
|
|
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
|
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
|
* by the Valve Corporation. You must obey the GNU General Public License in
|
|
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
|
* this exception to all derivative works. AlliedModders LLC defines further
|
|
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
|
* or <http://www.sourcemod.net/license.php>.
|
|
*
|
|
* Version: $Id$
|
|
*/
|
|
|
|
#define _USE_MATH_DEFINES
|
|
|
|
#include <sourcemod_version.h>
|
|
#include <cmath>
|
|
#include "extension.h"
|
|
#include "geoip_util.h"
|
|
|
|
// Log a message if the database is older than the set amount of days.
|
|
#define DATABASE_MAX_AGE 90
|
|
|
|
/**
|
|
* @file extension.cpp
|
|
* @brief Implement extension code here.
|
|
*/
|
|
GeoIP_Extension g_GeoIP;
|
|
MMDB_s mmdb;
|
|
|
|
SMEXT_LINK(&g_GeoIP);
|
|
|
|
bool GeoIP_Extension::SDK_OnLoad(char *error, size_t maxlength, bool late)
|
|
{
|
|
if (mmdb.filename) // Already loaded.
|
|
{
|
|
return true;
|
|
}
|
|
|
|
char m_GeoipDir[PLATFORM_MAX_PATH];
|
|
g_pSM->BuildPath(Path_SM, m_GeoipDir, sizeof(m_GeoipDir), "configs/geoip");
|
|
|
|
bool hasEntry = false;
|
|
|
|
IDirectory *dir = libsys->OpenDirectory(m_GeoipDir);
|
|
if (dir)
|
|
{
|
|
while (dir->MoreFiles())
|
|
{
|
|
if (dir->IsEntryFile())
|
|
{
|
|
const char *name = dir->GetEntryName();
|
|
size_t len = strlen(name);
|
|
if (len >= 5 && strcmp(&name[len-5], ".mmdb") == 0)
|
|
{
|
|
char database[PLATFORM_MAX_PATH];
|
|
libsys->PathFormat(database, sizeof(database), "%s/%s", m_GeoipDir, name);
|
|
|
|
int status = MMDB_open(database, MMDB_MODE_MMAP, &mmdb);
|
|
|
|
if (status != MMDB_SUCCESS)
|
|
{
|
|
ke::SafeSprintf(error, maxlength, "Failed to open GeoIP2 database %s: %s", database, MMDB_strerror(status));
|
|
libsys->CloseDirectory(dir);
|
|
return false;
|
|
}
|
|
|
|
hasEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
dir->NextEntry();
|
|
}
|
|
libsys->CloseDirectory(dir);
|
|
}
|
|
|
|
if (!hasEntry)
|
|
{
|
|
ke::SafeStrcpy(error, maxlength, "Could not find GeoIP2 database.");
|
|
return false;
|
|
}
|
|
|
|
g_pShareSys->AddNatives(myself, geoip_natives);
|
|
g_pShareSys->RegisterLibrary(myself, "GeoIP");
|
|
|
|
char date[40];
|
|
const time_t epoch = (const time_t)mmdb.metadata.build_epoch;
|
|
strftime(date, 40, "%F %T UTC", gmtime(&epoch));
|
|
|
|
g_pSM->LogMessage(myself, "GeoIP2 database loaded: %s (%s) (%s)", mmdb.metadata.database_type, date, mmdb.filename);
|
|
|
|
if (mmdb.metadata.languages.count > 0)
|
|
{
|
|
char buf[64];
|
|
for (size_t i = 0; i < mmdb.metadata.languages.count; i++)
|
|
{
|
|
if (i == 0)
|
|
{
|
|
strcpy(buf, mmdb.metadata.languages.names[i]);
|
|
}
|
|
else
|
|
{
|
|
strcat(buf, " ");
|
|
strcat(buf, mmdb.metadata.languages.names[i]);
|
|
}
|
|
}
|
|
|
|
g_pSM->LogMessage(myself, "GeoIP2 supported languages: %s", buf);
|
|
}
|
|
|
|
time_t now = time(NULL);
|
|
double days_since_update = difftime(now, epoch) / (60 * 60 * 24);
|
|
if (days_since_update > DATABASE_MAX_AGE)
|
|
smutils->LogMessage(myself, "Your database is older than %u days. You should consider downloading a newer version from e.g. https://dev.maxmind.com/geoip/geolite2-free-geolocation-data", DATABASE_MAX_AGE);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GeoIP_Extension::SDK_OnUnload()
|
|
{
|
|
MMDB_close(&mmdb);
|
|
}
|
|
|
|
const char *GeoIP_Extension::GetExtensionVerString()
|
|
{
|
|
return SOURCEMOD_VERSION;
|
|
}
|
|
|
|
const char *GeoIP_Extension::GetExtensionDateString()
|
|
{
|
|
return SOURCEMOD_BUILD_TIME;
|
|
}
|
|
|
|
/*******************************
|
|
* *
|
|
* GEOIP NATIVE IMPLEMENTATIONS *
|
|
* *
|
|
*******************************/
|
|
|
|
inline void StripPort(char *ip)
|
|
{
|
|
char *tmp = strchr(ip, ':');
|
|
if (!tmp)
|
|
return;
|
|
*tmp = '\0';
|
|
}
|
|
|
|
static cell_t sm_Geoip_Code2(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
const char *path[] = {"country", "iso_code", NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], 3, ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_Code3(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
const char *path[] = {"country", "iso_code", NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
for (size_t i = 0; i < SM_ARRAYSIZE(GeoIPCountryCode); i++)
|
|
{
|
|
if (!strncmp(ccode, GeoIPCountryCode[i], 2))
|
|
{
|
|
ccode = GeoIPCountryCode3[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
pCtx->StringToLocalUTF8(params[2], 4, ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_RegionCode(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
size_t length = 0;
|
|
char ccode[12] = { 0 };
|
|
|
|
const char *pathCountry[] = {"country", "iso_code", NULL};
|
|
std::string countryCode = lookupString(ip, pathCountry);
|
|
|
|
if (countryCode.length() != 0)
|
|
{
|
|
const char *pathRegion[] = {"subdivisions", "0", "iso_code", NULL};
|
|
std::string regionCode = lookupString(ip, pathRegion);
|
|
|
|
length = regionCode.length();
|
|
|
|
if (length != 0)
|
|
{
|
|
ke::SafeSprintf(ccode, sizeof(ccode), "%s-%s", countryCode.c_str(), regionCode.c_str());
|
|
}
|
|
}
|
|
|
|
pCtx->StringToLocalUTF8(params[2], sizeof(ccode), ccode, NULL);
|
|
|
|
return (length != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_ContinentCode(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
const char *path[] = {"continent", "code", NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], 3, ccode, NULL);
|
|
|
|
return getContinentId(ccode);
|
|
}
|
|
|
|
static cell_t sm_Geoip_Country(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
const char *path[] = {"country", "names", "en", NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], params[3], ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_CountryEx(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
if (params[4] > 0)
|
|
{
|
|
IGamePlayer *player = playerhelpers->GetGamePlayer(params[4]);
|
|
if (!player || !player->IsConnected())
|
|
{
|
|
return pCtx->ThrowNativeError("Invalid client index %d", params[4]);
|
|
}
|
|
}
|
|
|
|
const char *path[] = {"country", "names", getLang(params[4]), NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], params[3], ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_Continent(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
if (params[4] > 0)
|
|
{
|
|
IGamePlayer *player = playerhelpers->GetGamePlayer(params[4]);
|
|
if (!player || !player->IsConnected())
|
|
{
|
|
return pCtx->ThrowNativeError("Invalid client index %d", params[4]);
|
|
}
|
|
}
|
|
|
|
const char *path[] = {"continent", "names", getLang(params[4]), NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], params[3], ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_Region(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
if (params[4] > 0)
|
|
{
|
|
IGamePlayer *player = playerhelpers->GetGamePlayer(params[4]);
|
|
if (!player || !player->IsConnected())
|
|
{
|
|
return pCtx->ThrowNativeError("Invalid client index %d", params[4]);
|
|
}
|
|
}
|
|
|
|
const char *path[] = {"subdivisions", "0", "names", getLang(params[4]), NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], params[3], ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_City(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
if (params[4] > 0)
|
|
{
|
|
IGamePlayer *player = playerhelpers->GetGamePlayer(params[4]);
|
|
if (!player || !player->IsConnected())
|
|
{
|
|
return pCtx->ThrowNativeError("Invalid client index %d", params[4]);
|
|
}
|
|
}
|
|
|
|
const char *path[] = {"city", "names", getLang(params[4]), NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], params[3], ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_Timezone(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
const char *path[] = {"location", "time_zone", NULL};
|
|
std::string str = lookupString(ip, path);
|
|
const char *ccode = str.c_str();
|
|
|
|
pCtx->StringToLocalUTF8(params[2], params[3], ccode, NULL);
|
|
|
|
return (str.length() != 0) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t sm_Geoip_Latitude(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
const char *path[] = {"location", "latitude", NULL};
|
|
double latitude = lookupDouble(ip, path);
|
|
|
|
return sp_ftoc(latitude);
|
|
}
|
|
|
|
static cell_t sm_Geoip_Longitude(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char *ip;
|
|
pCtx->LocalToString(params[1], &ip);
|
|
StripPort(ip);
|
|
|
|
const char *path[] = {"location", "longitude", NULL};
|
|
double longitude = lookupDouble(ip, path);
|
|
|
|
return sp_ftoc(longitude);
|
|
}
|
|
|
|
static cell_t sm_Geoip_Distance(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
float earthRadius = params[5] ? 3958.0 : 6370.997; // miles / km
|
|
|
|
float lat1 = sp_ctof(params[1]) * (M_PI / 180);
|
|
float lon1 = sp_ctof(params[2]) * (M_PI / 180);
|
|
float lat2 = sp_ctof(params[3]) * (M_PI / 180);
|
|
float lon2 = sp_ctof(params[4]) * (M_PI / 180);
|
|
|
|
return sp_ftoc(earthRadius * acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1)));
|
|
}
|
|
|
|
const sp_nativeinfo_t geoip_natives[] =
|
|
{
|
|
{"GeoipCode2", sm_Geoip_Code2},
|
|
{"GeoipCode3", sm_Geoip_Code3},
|
|
{"GeoipRegionCode", sm_Geoip_RegionCode},
|
|
{"GeoipContinentCode", sm_Geoip_ContinentCode},
|
|
{"GeoipCountry", sm_Geoip_Country},
|
|
{"GeoipCountryEx", sm_Geoip_CountryEx},
|
|
{"GeoipContinent", sm_Geoip_Continent},
|
|
{"GeoipRegion", sm_Geoip_Region},
|
|
{"GeoipCity", sm_Geoip_City},
|
|
{"GeoipTimezone", sm_Geoip_Timezone},
|
|
{"GeoipLatitude", sm_Geoip_Latitude},
|
|
{"GeoipLongitude", sm_Geoip_Longitude},
|
|
{"GeoipDistance", sm_Geoip_Distance},
|
|
{NULL, NULL},
|
|
};
|
|
|