]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_geo_maxmind.cpp
Fix the Ubuntu package name for ssl_gnutls.
[user/henk/code/inspircd.git] / src / modules / extra / m_geo_maxmind.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019-2021 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: require_version("libmaxminddb" "0" "1.2.1") warning("The version of libmaxminddb you are using may cause a segmentation fault if given a corrupt database file!")
21 /// $CompilerFlags: find_compiler_flags("libmaxminddb" "")
22
23 /// $LinkerFlags: find_linker_flags("libmaxminddb" "-lmaxminddb")
24
25 /// $PackageInfo: require_system("arch") libmaxminddb pkgconf
26 /// $PackageInfo: require_system("darwin") libmaxminddb pkg-config
27 /// $PackageInfo: require_system("debian" "9.0") libmaxminddb-dev pkg-config
28 /// $PackageInfo: require_system("ubuntu" "16.04") libmaxminddb-dev pkg-config
29
30 #ifdef _WIN32
31 # pragma comment(lib, "libmaxminddb.lib")
32 #endif
33
34 #include "inspircd.h"
35 #include "modules/geolocation.h"
36 #include <maxminddb.h>
37
38 class GeolocationExtItem : public ExtensionItem
39 {
40  public:
41         GeolocationExtItem(Module* parent)
42                 : ExtensionItem("geolocation", ExtensionItem::EXT_USER, parent)
43         {
44         }
45
46         std::string ToHuman(const Extensible* container, void* item) const CXX11_OVERRIDE
47         {
48                 Geolocation::Location* location = static_cast<Geolocation::Location*>(item);
49                 return location->GetName() + " [" + location->GetCode() + "]";
50         }
51
52         void free(Extensible* container, void* item) CXX11_OVERRIDE
53         {
54                 Geolocation::Location* old = static_cast<Geolocation::Location*>(item);
55                 if (old)
56                         old->refcount_dec();
57         }
58
59         Geolocation::Location* get(const Extensible* item) const
60         {
61                 return static_cast<Geolocation::Location*>(get_raw(item));
62         }
63
64         void set(Extensible* item, Geolocation::Location* value)
65         {
66                 value->refcount_inc();
67                 free(item, set_raw(item, value));
68         }
69
70         void unset(Extensible* container)
71         {
72                 free(container, unset_raw(container));
73         }
74 };
75
76 typedef insp::flat_map<std::string, Geolocation::Location*> LocationMap;
77
78 class GeolocationAPIImpl : public Geolocation::APIBase
79 {
80  public:
81         GeolocationExtItem ext;
82         LocationMap locations;
83         MMDB_s mmdb;
84
85         GeolocationAPIImpl(Module* parent)
86                 : Geolocation::APIBase(parent)
87                 , ext(parent)
88         {
89         }
90
91         Geolocation::Location* GetLocation(User* user) CXX11_OVERRIDE
92         {
93                 // If we have the location cached then use that instead.
94                 Geolocation::Location* location = ext.get(user);
95                 if (location)
96                         return location;
97
98                 // Attempt to locate this user.
99                 location = GetLocation(user->client_sa);
100                 if (!location)
101                         return NULL;
102
103                 // We found the user. Cache their location for future use.
104                 ext.set(user, location);
105                 return location;
106         }
107
108         Geolocation::Location* GetLocation(irc::sockets::sockaddrs& sa) CXX11_OVERRIDE
109         {
110                 // Skip trying to look up a UNIX socket.
111                 if (sa.family() != AF_INET && sa.family() != AF_INET6)
112                         return NULL;
113
114                 // Attempt to look up the socket address.
115                 int result;
116                 MMDB_lookup_result_s lookup = MMDB_lookup_sockaddr(&mmdb, &sa.sa, &result);
117                 if (result != MMDB_SUCCESS || !lookup.found_entry)
118                         return NULL;
119
120                 // Attempt to retrieve the country code.
121                 MMDB_entry_data_s country_code;
122                 result = MMDB_get_value(&lookup.entry, &country_code, "country", "iso_code", NULL);
123                 if (result != MMDB_SUCCESS || !country_code.has_data || country_code.type != MMDB_DATA_TYPE_UTF8_STRING || country_code.data_size != 2)
124                         return NULL;
125
126                 // If the country has been seen before then use our cached Location object.
127                 const std::string code(country_code.utf8_string, country_code.data_size);
128                 LocationMap::iterator liter = locations.find(code);
129                 if (liter != locations.end())
130                         return liter->second;
131
132                 // Attempt to retrieve the country name.
133                 MMDB_entry_data_s country_name;
134                 result = MMDB_get_value(&lookup.entry, &country_name, "country", "names", "en", NULL);
135                 if (result != MMDB_SUCCESS || !country_name.has_data || country_name.type != MMDB_DATA_TYPE_UTF8_STRING)
136                         return NULL;
137
138                 // Create a Location object and cache it.
139                 const std::string cname(country_name.utf8_string, country_name.data_size);
140                 Geolocation::Location* location = new Geolocation::Location(code, cname);
141                 locations[code] = location;
142                 return location;
143         }
144 };
145
146 class ModuleGeoMaxMind : public Module
147 {
148  private:
149         GeolocationAPIImpl geoapi;
150
151  public:
152         ModuleGeoMaxMind()
153                 : geoapi(this)
154         {
155                 memset(&geoapi.mmdb, 0, sizeof(geoapi.mmdb));
156         }
157
158         ~ModuleGeoMaxMind()
159         {
160                 MMDB_close(&geoapi.mmdb);
161         }
162
163         Version GetVersion() CXX11_OVERRIDE
164         {
165                 return Version("Allows the server to perform geolocation lookups on both IP addresses and users.", VF_VENDOR);
166         }
167
168         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
169         {
170                 ConfigTag* tag = ServerInstance->Config->ConfValue("maxmind");
171                 const std::string file = ServerInstance->Config->Paths.PrependConfig(tag->getString("file", "GeoLite2-Country.mmdb", 1));
172
173                 // Try to read the new database.
174                 MMDB_s mmdb;
175                 int result = MMDB_open(file.c_str(), MMDB_MODE_MMAP, &mmdb);
176                 if (result != MMDB_SUCCESS)
177                         throw ModuleException(InspIRCd::Format("Unable to load the MaxMind database (%s): %s",
178                                 file.c_str(), MMDB_strerror(result)));
179
180                 // Swap the new database with the old database.
181                 std::swap(mmdb, geoapi.mmdb);
182
183                 // Free the old database.
184                 MMDB_close(&mmdb);
185         }
186
187         void OnGarbageCollect() CXX11_OVERRIDE
188         {
189                 for (LocationMap::iterator iter = geoapi.locations.begin(); iter != geoapi.locations.end(); )
190                 {
191                         Geolocation::Location* location = iter->second;
192                         if (location->GetUseCount())
193                         {
194                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Preserving geolocation data for %s (%s) with use count %u... ",
195                                         location->GetName().c_str(), location->GetCode().c_str(), location->GetUseCount());
196                                 iter++;
197                         }
198                         else
199                         {
200                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Deleting unused geolocation data for %s (%s)",
201                                         location->GetName().c_str(), location->GetCode().c_str());
202                                 delete location;
203                                 iter = geoapi.locations.erase(iter);
204                         }
205                 }
206         }
207
208         void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE
209         {
210                 // Unset the extension so that the location of this user is looked
211                 // up again next time it is requested.
212                 geoapi.ext.unset(user);
213         }
214 };
215
216 MODULE_INIT(ModuleGeoMaxMind)