2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2019 Sadie Powell <sadie@witchery.services>
5 * Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
7 * This file is part of InspIRCd. InspIRCd is free software: you can
8 * redistribute it and/or modify it under the terms of the GNU General Public
9 * License as published by the Free Software Foundation, version 2.
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 /// $CompilerFlags: find_compiler_flags("libmaxminddb" "")
21 /// $LinkerFlags: find_linker_flags("libmaxminddb" "-lmaxminddb")
23 /// $PackageInfo: require_system("arch") libmaxminddb pkgconf
24 /// $PackageInfo: require_system("darwin") libmaxminddb pkg-config
25 /// $PackageInfo: require_system("debian" "9.0") libmaxminddb-dev pkg-config
26 /// $PackageInfo: require_system("ubuntu" "16.04") libmaxminddb-dev pkg-config
29 # pragma comment(lib, "libmaxminddb.lib")
33 #include "modules/geolocation.h"
34 #include <maxminddb.h>
36 class GeolocationExtItem : public ExtensionItem
39 GeolocationExtItem(Module* parent)
40 : ExtensionItem("geolocation", ExtensionItem::EXT_USER, parent)
44 void free(Extensible* container, void* item) CXX11_OVERRIDE
46 Geolocation::Location* old = static_cast<Geolocation::Location*>(item);
51 Geolocation::Location* get(const Extensible* item) const
53 return static_cast<Geolocation::Location*>(get_raw(item));
56 void set(Extensible* item, Geolocation::Location* value)
58 value->refcount_inc();
59 free(item, set_raw(item, value));
62 void unset(Extensible* container)
64 free(container, unset_raw(container));
68 typedef insp::flat_map<std::string, Geolocation::Location*> LocationMap;
70 class GeolocationAPIImpl : public Geolocation::APIBase
73 GeolocationExtItem ext;
74 LocationMap locations;
77 GeolocationAPIImpl(Module* parent)
78 : Geolocation::APIBase(parent)
83 Geolocation::Location* GetLocation(User* user) CXX11_OVERRIDE
85 // If we have the location cached then use that instead.
86 Geolocation::Location* location = ext.get(user);
90 // Attempt to locate this user.
91 location = GetLocation(user->client_sa);
95 // We found the user. Cache their location for future use.
96 ext.set(user, location);
100 Geolocation::Location* GetLocation(irc::sockets::sockaddrs& sa) CXX11_OVERRIDE
102 // Skip trying to look up a UNIX socket.
103 if (sa.family() != AF_INET && sa.family() != AF_INET6)
106 // Attempt to look up the socket address.
108 MMDB_lookup_result_s lookup = MMDB_lookup_sockaddr(&mmdb, &sa.sa, &result);
109 if (result != MMDB_SUCCESS || !lookup.found_entry)
112 // Attempt to retrieve the country code.
113 MMDB_entry_data_s country_code;
114 result = MMDB_get_value(&lookup.entry, &country_code, "country", "iso_code", NULL);
115 if (result != MMDB_SUCCESS || !country_code.has_data || country_code.type != MMDB_DATA_TYPE_UTF8_STRING || country_code.data_size != 2)
118 // If the country has been seen before then use our cached Location object.
119 const std::string code(country_code.utf8_string, country_code.data_size);
120 LocationMap::iterator liter = locations.find(code);
121 if (liter != locations.end())
122 return liter->second;
124 // Attempt to retrieve the country name.
125 MMDB_entry_data_s country_name;
126 result = MMDB_get_value(&lookup.entry, &country_name, "country", "names", "en", NULL);
127 if (result != MMDB_SUCCESS || !country_name.has_data || country_name.type != MMDB_DATA_TYPE_UTF8_STRING)
130 // Create a Location object and cache it.
131 const std::string cname(country_name.utf8_string, country_name.data_size);
132 Geolocation::Location* location = new Geolocation::Location(code, cname);
133 locations[code] = location;
138 class ModuleGeoMaxMind : public Module
141 GeolocationAPIImpl geoapi;
147 memset(&geoapi.mmdb, 0, sizeof(geoapi.mmdb));
152 MMDB_close(&geoapi.mmdb);
155 Version GetVersion() CXX11_OVERRIDE
157 return Version("Provides Geolocation lookups using the libMaxMindDB library", VF_VENDOR);
160 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
162 ConfigTag* tag = ServerInstance->Config->ConfValue("maxmind");
163 const std::string file = ServerInstance->Config->Paths.PrependConfig(tag->getString("file", "GeoLite2-Country.mmdb"));
165 // Try to read the new database.
167 int result = MMDB_open(file.c_str(), MMDB_MODE_MMAP, &mmdb);
168 if (result != MMDB_SUCCESS)
169 throw ModuleException(InspIRCd::Format("Unable to load the MaxMind database (%s): %s",
170 file.c_str(), MMDB_strerror(result)));
172 // Swap the new database with the old database.
173 std::swap(mmdb, geoapi.mmdb);
175 // Free the old database.
179 void OnGarbageCollect() CXX11_OVERRIDE
181 for (LocationMap::iterator iter = geoapi.locations.begin(); iter != geoapi.locations.end(); )
183 Geolocation::Location* location = iter->second;
184 if (location->GetUseCount())
186 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Preserving geolocation data for %s (%s) with use count %u... ",
187 location->GetName().c_str(), location->GetCode().c_str(), location->GetUseCount());
192 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Deleting unused geolocation data for %s (%s)",
193 location->GetName().c_str(), location->GetCode().c_str());
195 iter = geoapi.locations.erase(iter);
200 void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE
202 // Unset the extension so that the location of this user is looked
203 // up again next time it is requested.
204 geoapi.ext.unset(user);
208 MODULE_INIT(ModuleGeoMaxMind)