diff options
Diffstat (limited to 'src/modules/extra/m_geo_maxmind.cpp')
-rw-r--r-- | src/modules/extra/m_geo_maxmind.cpp | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/src/modules/extra/m_geo_maxmind.cpp b/src/modules/extra/m_geo_maxmind.cpp new file mode 100644 index 000000000..b7639c287 --- /dev/null +++ b/src/modules/extra/m_geo_maxmind.cpp @@ -0,0 +1,202 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2019 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd 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, version 2. + * + * 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/>. + */ + +/// $CompilerFlags: find_compiler_flags("libmaxminddb") +/// $LinkerFlags: find_linker_flags("libmaxminddb") + +/// $PackageInfo: require_system("darwin") libmaxminddb pkg-config +/// $PackageInfo: require_system("debian" "9.0") libmaxminddb-dev pkg-config +/// $PackageInfo: require_system("ubuntu" "16.04") libmaxminddb-dev pkg-config + +#ifdef _WIN32 +# pragma comment(lib, "libmaxminddb.lib") +#endif + +#include <maxminddb.h> +#include "inspircd.h" +#include "modules/geolocation.h" + +class GeolocationExtItem : public LocalExtItem +{ + public: + GeolocationExtItem(Module* parent) + : LocalExtItem("geolocation", ExtensionItem::EXT_USER, parent) + { + } + + void free(Extensible* container, void* item) CXX11_OVERRIDE + { + Geolocation::Location* old = static_cast<Geolocation::Location*>(item); + if (old) + old->refcount_dec(); + } + + Geolocation::Location* get(const Extensible* item) const + { + return static_cast<Geolocation::Location*>(get_raw(item)); + } + + void set(Extensible* item, Geolocation::Location* value) + { + value->refcount_inc(); + free(item, set_raw(item, value)); + } + + void unset(Extensible* container) + { + free(container, unset_raw(container)); + } +}; + +typedef insp::flat_map<std::string, Geolocation::Location*> LocationMap; + +class GeolocationAPIImpl : public Geolocation::APIBase +{ + public: + GeolocationExtItem ext; + LocationMap locations; + MMDB_s mmdb; + + GeolocationAPIImpl(Module* parent) + : Geolocation::APIBase(parent) + , ext(parent) + { + } + + Geolocation::Location* GetLocation(User* user) CXX11_OVERRIDE + { + // If we have the location cached then use that instead. + Geolocation::Location* location = ext.get(user); + if (location) + return location; + + // Attempt to locate this user. + location = GetLocation(user->client_sa); + if (!location) + return NULL; + + // We found the user. Cache their location for future use. + ext.set(user, location); + return location; + } + + Geolocation::Location* GetLocation(irc::sockets::sockaddrs& sa) CXX11_OVERRIDE + { + // Attempt to look up the socket address. + int result; + MMDB_lookup_result_s lookup = MMDB_lookup_sockaddr(&mmdb, &sa.sa, &result); + if (result != MMDB_SUCCESS || !lookup.found_entry) + return NULL; + + // Attempt to retrieve the country code. + MMDB_entry_data_s country_code; + result = MMDB_get_value(&lookup.entry, &country_code, "country", "iso_code", NULL); + if (result != MMDB_SUCCESS || !country_code.has_data || country_code.type != MMDB_DATA_TYPE_UTF8_STRING || country_code.data_size != 2) + return NULL; + + // If the country has been seen before then use our cached Location object. + const std::string code(country_code.utf8_string, country_code.data_size); + LocationMap::iterator liter = locations.find(code); + if (liter != locations.end()) + return liter->second; + + // Attempt to retrieve the country name. + MMDB_entry_data_s country_name; + result = MMDB_get_value(&lookup.entry, &country_name, "country", "names", "en", NULL); + if (result != MMDB_SUCCESS || !country_name.has_data || country_name.type != MMDB_DATA_TYPE_UTF8_STRING) + return NULL; + + // Create a Location object and cache it. + const std::string cname(country_name.utf8_string, country_name.data_size); + Geolocation::Location* location = new Geolocation::Location(code, cname); + locations[code] = location; + return location; + } +}; + +class ModuleGeoMaxMind : public Module +{ + private: + GeolocationAPIImpl geoapi; + + public: + ModuleGeoMaxMind() + : geoapi(this) + { + memset(&geoapi.mmdb, 0, sizeof(geoapi.mmdb)); + } + + ~ModuleGeoMaxMind() + { + MMDB_close(&geoapi.mmdb); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides Geolocation lookups using the libMaxMindDB library", VF_VENDOR); + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("maxmind"); + const std::string file = ServerInstance->Config->Paths.PrependConfig(tag->getString("file", "GeoLite2-Country.mmdb")); + + // Try to read the new database. + MMDB_s mmdb; + int result = MMDB_open(file.c_str(), MMDB_MODE_MMAP, &mmdb); + if (result != MMDB_SUCCESS) + throw ModuleException(InspIRCd::Format("Unable to load the MaxMind database (%s): %s", + file.c_str(), MMDB_strerror(result))); + + // Swap the new database with the old database. + std::swap(mmdb, geoapi.mmdb); + + // Free the old database. + MMDB_close(&mmdb); + } + + void OnGarbageCollect() CXX11_OVERRIDE + { + for (LocationMap::iterator iter = geoapi.locations.begin(); iter != geoapi.locations.end(); ) + { + Geolocation::Location* location = iter->second; + if (location->GetUseCount()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Preserving geolocation data for %s (%s) with use count %u... ", + location->GetName().c_str(), location->GetCode().c_str(), location->GetUseCount()); + iter++; + } + else + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Deleting unused geolocation data for %s (%s)", + location->GetName().c_str(), location->GetCode().c_str()); + delete location; + iter = geoapi.locations.erase(iter); + } + } + } + + void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE + { + // Unset the extension so that the location of this user is looked + // up again next time it is requested. + geoapi.ext.unset(user); + } +}; + +MODULE_INIT(ModuleGeoMaxMind) |