]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_geo_maxmind.cpp
Update my name and email address.
[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  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 /// $CompilerFlags: find_compiler_flags("libmaxminddb" "")
20 /// $LinkerFlags: find_linker_flags("libmaxminddb" "-lmaxminddb")
21
22 /// $PackageInfo: require_system("arch") libmaxminddb pkgconf
23 /// $PackageInfo: require_system("darwin") libmaxminddb pkg-config
24 /// $PackageInfo: require_system("debian" "9.0") libmaxminddb-dev pkg-config
25 /// $PackageInfo: require_system("ubuntu" "16.04") libmaxminddb-dev pkg-config
26
27 #ifdef _WIN32
28 # pragma comment(lib, "libmaxminddb.lib")
29 #endif
30
31 #include "inspircd.h"
32 #include "modules/geolocation.h"
33 #include <maxminddb.h>
34
35 class GeolocationExtItem : public ExtensionItem
36 {
37  public:
38         GeolocationExtItem(Module* parent)
39                 : ExtensionItem("geolocation", ExtensionItem::EXT_USER, parent)
40         {
41         }
42
43         void free(Extensible* container, void* item) CXX11_OVERRIDE
44         {
45                 Geolocation::Location* old = static_cast<Geolocation::Location*>(item);
46                 if (old)
47                         old->refcount_dec();
48         }
49
50         Geolocation::Location* get(const Extensible* item) const
51         {
52                 return static_cast<Geolocation::Location*>(get_raw(item));
53         }
54
55         void set(Extensible* item, Geolocation::Location* value)
56         {
57                 value->refcount_inc();
58                 free(item, set_raw(item, value));
59         }
60
61         void unset(Extensible* container)
62         {
63                 free(container, unset_raw(container));
64         }
65 };
66
67 typedef insp::flat_map<std::string, Geolocation::Location*> LocationMap;
68
69 class GeolocationAPIImpl : public Geolocation::APIBase
70 {
71  public:
72         GeolocationExtItem ext;
73         LocationMap locations;
74         MMDB_s mmdb;
75
76         GeolocationAPIImpl(Module* parent)
77                 : Geolocation::APIBase(parent)
78                 , ext(parent)
79         {
80         }
81
82         Geolocation::Location* GetLocation(User* user) CXX11_OVERRIDE
83         {
84                 // If we have the location cached then use that instead.
85                 Geolocation::Location* location = ext.get(user);
86                 if (location)
87                         return location;
88
89                 // Attempt to locate this user.
90                 location = GetLocation(user->client_sa);
91                 if (!location)
92                         return NULL;
93
94                 // We found the user. Cache their location for future use.
95                 ext.set(user, location);
96                 return location;
97         }
98
99         Geolocation::Location* GetLocation(irc::sockets::sockaddrs& sa) CXX11_OVERRIDE
100         {
101                 // Skip trying to look up a UNIX socket.
102                 if (sa.family() != AF_INET && sa.family() != AF_INET6)
103                         return NULL;
104
105                 // Attempt to look up the socket address.
106                 int result;
107                 MMDB_lookup_result_s lookup = MMDB_lookup_sockaddr(&mmdb, &sa.sa, &result);
108                 if (result != MMDB_SUCCESS || !lookup.found_entry)
109                         return NULL;
110
111                 // Attempt to retrieve the country code.
112                 MMDB_entry_data_s country_code;
113                 result = MMDB_get_value(&lookup.entry, &country_code, "country", "iso_code", NULL);
114                 if (result != MMDB_SUCCESS || !country_code.has_data || country_code.type != MMDB_DATA_TYPE_UTF8_STRING || country_code.data_size != 2)
115                         return NULL;
116
117                 // If the country has been seen before then use our cached Location object.
118                 const std::string code(country_code.utf8_string, country_code.data_size);
119                 LocationMap::iterator liter = locations.find(code);
120                 if (liter != locations.end())
121                         return liter->second;
122
123                 // Attempt to retrieve the country name.
124                 MMDB_entry_data_s country_name;
125                 result = MMDB_get_value(&lookup.entry, &country_name, "country", "names", "en", NULL);
126                 if (result != MMDB_SUCCESS || !country_name.has_data || country_name.type != MMDB_DATA_TYPE_UTF8_STRING)
127                         return NULL;
128
129                 // Create a Location object and cache it.
130                 const std::string cname(country_name.utf8_string, country_name.data_size);
131                 Geolocation::Location* location = new Geolocation::Location(code, cname);
132                 locations[code] = location;
133                 return location;
134         }
135 };
136
137 class ModuleGeoMaxMind : public Module
138 {
139  private:
140         GeolocationAPIImpl geoapi;
141
142  public:
143         ModuleGeoMaxMind()
144                 : geoapi(this)
145         {
146                 memset(&geoapi.mmdb, 0, sizeof(geoapi.mmdb));
147         }
148
149         ~ModuleGeoMaxMind()
150         {
151                 MMDB_close(&geoapi.mmdb);
152         }
153
154         Version GetVersion() CXX11_OVERRIDE
155         {
156                 return Version("Provides Geolocation lookups using the libMaxMindDB library", VF_VENDOR);
157         }
158
159         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
160         {
161                 ConfigTag* tag = ServerInstance->Config->ConfValue("maxmind");
162                 const std::string file = ServerInstance->Config->Paths.PrependConfig(tag->getString("file", "GeoLite2-Country.mmdb"));
163
164                 // Try to read the new database.
165                 MMDB_s mmdb;
166                 int result = MMDB_open(file.c_str(), MMDB_MODE_MMAP, &mmdb);
167                 if (result != MMDB_SUCCESS)
168                         throw ModuleException(InspIRCd::Format("Unable to load the MaxMind database (%s): %s",
169                                 file.c_str(), MMDB_strerror(result)));
170
171                 // Swap the new database with the old database.
172                 std::swap(mmdb, geoapi.mmdb);
173
174                 // Free the old database.
175                 MMDB_close(&mmdb);
176         }
177
178         void OnGarbageCollect() CXX11_OVERRIDE
179         {
180                 for (LocationMap::iterator iter = geoapi.locations.begin(); iter != geoapi.locations.end(); )
181                 {       
182                         Geolocation::Location* location = iter->second;
183                         if (location->GetUseCount())
184                         {
185                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Preserving geolocation data for %s (%s) with use count %u... ",
186                                         location->GetName().c_str(), location->GetCode().c_str(), location->GetUseCount());
187                                 iter++;
188                         }
189                         else
190                         {
191                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Deleting unused geolocation data for %s (%s)",
192                                         location->GetName().c_str(), location->GetCode().c_str());
193                                 delete location;
194                                 iter = geoapi.locations.erase(iter);
195                         }
196                 }
197         }
198
199         void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE
200         {
201                 // Unset the extension so that the location of this user is looked
202                 // up again next time it is requested.
203                 geoapi.ext.unset(user);
204         }
205 };
206
207 MODULE_INIT(ModuleGeoMaxMind)