sourcemod/extensions/geoip/extension.cpp
peace-maker 5e3a189642
Log a notice if the geoip database gets too old (#1791)
Since we ship an ancient version of the database, help server operators keep track of the database version.
2022-06-30 08:22:51 +02:00

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},
};