diff --git a/4/m_whoisgeolite.cpp b/4/m_whoisgeolite.cpp new file mode 100644 index 00000000..789bb61a --- /dev/null +++ b/4/m_whoisgeolite.cpp @@ -0,0 +1,116 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2024 reverse + * + * This file contains a third-party module for InspIRCd. 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 . + */ + +/// $ModAuthor: reverse +/// $ModDesc: Adds city and country information to WHOIS using the MaxMind database. +/// $ModConfig: +/// $ModDepends: core 4 + + +/// $CompilerFlags: find_compiler_flags("libmaxminddb") +/// $LinkerFlags: find_linker_flags("libmaxminddb") + +/// $PackageInfo: require_system("alpine") libmaxminddb-dev pkgconf +/// $PackageInfo: require_system("arch") pkgconf libmaxminddb +/// $PackageInfo: require_system("darwin") libmaxminddb pkg-config +/// $PackageInfo: require_system("debian~") libmaxminddb-dev pkg-config +/// $PackageInfo: require_system("rhel~") pkg-config libmaxminddb-devel + +#include "inspircd.h" +#include "modules/whois.h" +#include "extension.h" +#include + +class ModuleWhoisGeoLite final : public Module, public Whois::EventListener +{ +private: + MMDB_s mmdb; // MaxMind database object + std::string dbpath; // Path to the GeoLite2 database + StringExtItem country_item; // Extension item for storing city and country info + +public: + ModuleWhoisGeoLite() + : Module(VF_OPTCOMMON, "Adds city and country information to WHOIS using the MaxMind database.") + , Whois::EventListener(this) + , country_item(this, "geo-lite-country", ExtensionType::USER, true) // Sync Servers + { + } + + void ReadConfig(ConfigStatus& status) override + { + auto& tag = ServerInstance->Config->ConfValue("geolite"); + dbpath = ServerInstance->Config->Paths.PrependConfig(tag->getString("dbpath", "data/GeoLite2-City.mmdb")); + + int status_open = MMDB_open(dbpath.c_str(), MMDB_MODE_MMAP, &mmdb); + if (status_open != MMDB_SUCCESS) { + std::string error_msg = "GeoLite2: Failed to open GeoLite2 database: " + std::string(MMDB_strerror(status_open)); + throw ModuleException(this, error_msg.c_str()); + } + } + + void OnWhois(Whois::Context& whois) override + { + User* source = whois.GetSource(); + User* target = whois.GetTarget(); + + if (!source->IsOper()) + return; + + const std::string* info = country_item.Get(target); + if (info && !info->empty()) { + whois.SendLine(RPL_WHOISSPECIAL, "is connecting from " + *info); + } else { + whois.SendLine(RPL_WHOISSPECIAL, "City: Unknown, Country: Unknown"); + } + } + + void OnChangeRemoteAddress(LocalUser* user) override + { + if (!user->client_sa.is_ip()) { + country_item.Unset(user); + return; + } + + int gai_error = 0; + const struct sockaddr* addr = &user->client_sa.sa; + MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&mmdb, addr, &gai_error); + + if (gai_error != 0 || !result.found_entry) { + country_item.Unset(user); + return; + } + + MMDB_entry_data_s city_data = {}; + MMDB_entry_data_s country_data = {}; + int status_city = MMDB_get_value(&result.entry, &city_data, "city", "names", "en", nullptr); + int status_country = MMDB_get_value(&result.entry, &country_data, "country", "names", "en", nullptr); + + std::string city = (status_city == MMDB_SUCCESS && city_data.has_data) ? std::string(city_data.utf8_string, city_data.data_size) : "Unknown"; + std::string country = (status_country == MMDB_SUCCESS && country_data.has_data) ? std::string(country_data.utf8_string, country_data.data_size) : "Unknown"; + + country_item.Set(user, "City: " + city + ", Country: " + country); + } + + ~ModuleWhoisGeoLite() override + { + MMDB_close(&mmdb); + } +}; + +MODULE_INIT(ModuleWhoisGeoLite) +