]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_geo_maxmind.cpp
0cf082775b911a7283bd5fee4a6c86e622540946
[user/henk/code/inspircd.git] / src / modules / extra / m_geo_maxmind.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 Sadie Powell <sadie@witchery.services>
5  *   Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
6  *
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.
10  *
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
14  * details.
15  *
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/>.
18  */
19
20 /// $CompilerFlags: find_compiler_flags("libmaxminddb" "")
21 /// $LinkerFlags: find_linker_flags("libmaxminddb" "-lmaxminddb")
22
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
27
28 #ifdef _WIN32
29 # pragma comment(lib, "libmaxminddb.lib")
30 #endif
31
32 #include "inspircd.h"
33 #include "modules/geolocation.h"
34 #include <maxminddb.h>
35
36 class GeolocationExtItem : public ExtensionItem
37 {
38  public:
39         GeolocationExtItem(Module* parent)
40                 : ExtensionItem("geolocation", ExtensionItem::EXT_USER, parent)
41         {
42         }
43
44         void free(Extensible* container, void* item) CXX11_OVERRIDE
45         {
46                 Geolocation::Location* old = static_cast<Geolocation::Location*>(item);
47                 if (old)
48                         old->refcount_dec();
49         }
50
51         Geolocation::Location* get(const Extensible* item) const
52         {
53                 return static_cast<Geolocation::Location*>(get_raw(item));
54         }
55
56         void set(Extensible* item, Geolocation::Location* value)
57         {
58                 value->refcount_inc();
59                 free(item, set_raw(item, value));
60         }
61
62         void unset(Extensible* container)
63         {
64                 free(container, unset_raw(container));
65         }
66 };
67
68 typedef insp::flat_map<std::string, Geolocation::Location*> LocationMap;
69
70 class GeolocationAPIImpl : public Geolocation::APIBase
71 {
72  public:
73         GeolocationExtItem ext;
74         LocationMap locations;
75         MMDB_s mmdb;
76
77         GeolocationAPIImpl(Module* parent)
78                 : Geolocation::APIBase(parent)
79                 , ext(parent)
80         {
81         }
82
83         Geolocation::Location* GetLocation(User* user) CXX11_OVERRIDE
84         {
85                 // If we have the location cached then use that instead.
86                 Geolocation::Location* location = ext.get(user);
87                 if (location)
88                         return location;
89
90                 // Attempt to locate this user.
91                 location = GetLocation(user->client_sa);
92                 if (!location)
93                         return NULL;
94
95                 // We found the user. Cache their location for future use.
96                 ext.set(user, location);
97                 return location;
98         }
99
100         Geolocation::Location* GetLocation(irc::sockets::sockaddrs& sa) CXX11_OVERRIDE
101         {
102                 // Skip trying to look up a UNIX socket.
103                 if (sa.family() != AF_INET && sa.family() != AF_INET6)
104                         return NULL;
105
106                 // Attempt to look up the socket address.
107                 int result;
108                 MMDB_lookup_result_s lookup = MMDB_lookup_sockaddr(&mmdb, &sa.sa, &result);
109                 if (result != MMDB_SUCCESS || !lookup.found_entry)
110                         return NULL;
111
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)
116                         return NULL;
117
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;
123
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)
128                         return NULL;
129
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;
134                 return location;
135         }
136 };
137
138 class ModuleGeoMaxMind : public Module
139 {
140  private:
141         GeolocationAPIImpl geoapi;
142
143  public:
144         ModuleGeoMaxMind()
145                 : geoapi(this)
146         {
147                 memset(&geoapi.mmdb, 0, sizeof(geoapi.mmdb));
148         }
149
150         ~ModuleGeoMaxMind()
151         {
152                 MMDB_close(&geoapi.mmdb);
153         }
154
155         Version GetVersion() CXX11_OVERRIDE
156         {
157                 return Version("Provides Geolocation lookups using the libMaxMindDB library", VF_VENDOR);
158         }
159
160         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
161         {
162                 ConfigTag* tag = ServerInstance->Config->ConfValue("maxmind");
163                 const std::string file = ServerInstance->Config->Paths.PrependConfig(tag->getString("file", "GeoLite2-Country.mmdb"));
164
165                 // Try to read the new database.
166                 MMDB_s mmdb;
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)));
171
172                 // Swap the new database with the old database.
173                 std::swap(mmdb, geoapi.mmdb);
174
175                 // Free the old database.
176                 MMDB_close(&mmdb);
177         }
178
179         void OnGarbageCollect() CXX11_OVERRIDE
180         {
181                 for (LocationMap::iterator iter = geoapi.locations.begin(); iter != geoapi.locations.end(); )
182                 {
183                         Geolocation::Location* location = iter->second;
184                         if (location->GetUseCount())
185                         {
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());
188                                 iter++;
189                         }
190                         else
191                         {
192                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Deleting unused geolocation data for %s (%s)",
193                                         location->GetName().c_str(), location->GetCode().c_str());
194                                 delete location;
195                                 iter = geoapi.locations.erase(iter);
196                         }
197                 }
198         }
199
200         void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE
201         {
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);
205         }
206 };
207
208 MODULE_INIT(ModuleGeoMaxMind)