mirror of
https://github.com/inspircd/inspircd.git
synced 2025-03-10 02:59:01 -04:00
365 lines
11 KiB
C++
365 lines
11 KiB
C++
/*
|
|
* InspIRCd -- Internet Relay Chat Daemon
|
|
*
|
|
* Copyright (C) 2023-2024 Sadie Powell <sadie@witchery.services>
|
|
* Copyright (C) 2017 Adam <Adam@anope.org>
|
|
*
|
|
* This file is part of InspIRCd. InspIRCd is free software: 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
/// $CompilerFlags: require_library("libpsl") find_compiler_flags("libpsl") -DHAS_LIBPSL
|
|
/// $LinkerFlags: require_library("libpsl") find_linker_flags("libpsl")
|
|
|
|
/// $PackageInfo: require_system("alpine") libpsl-dev pkgconf
|
|
/// $PackageInfo: require_system("arch") libpsl pkgconf
|
|
/// $PackageInfo: require_system("darwin") libpsl pkg-config
|
|
/// $PackageInfo: require_system("debian~") libpsl-dev pkg-config
|
|
/// $PackageInfo: require_system("rhel~") libpsl-devel pkg-config
|
|
|
|
#ifdef _WIN32
|
|
# if __has_include(<libpsl.h>)
|
|
# define HAS_LIBPSL
|
|
# pragma comment(lib, "psl.lib")
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef HAS_LIBPSL
|
|
# include <libpsl.h>
|
|
#else
|
|
typedef void psl_ctx_t;
|
|
#endif
|
|
|
|
#include "inspircd.h"
|
|
#include "modules/cloak.h"
|
|
#include "modules/hash.h"
|
|
#include "utility/string.h"
|
|
|
|
class SHA256Method final
|
|
: public Cloak::Method
|
|
{
|
|
private:
|
|
// The base32 table used for upper-case cloaks.
|
|
static constexpr unsigned char base32upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
|
|
// The base32 table used for lower-case cloaks.
|
|
static constexpr unsigned char base32lower[] = "abcdefghijklmnopqrstuvwxyz234567";
|
|
|
|
// The number of bytes of the hash to use when downsampling.
|
|
static constexpr size_t segmentlen = 8;
|
|
|
|
// Whether to cloak the hostname if available.
|
|
const bool cloakhost;
|
|
|
|
// The number of parts of the hostname shown.
|
|
const unsigned long hostparts;
|
|
|
|
// The secret used for generating cloaks.
|
|
const std::string key;
|
|
|
|
// The number of parts of the UNIX socket path shown.
|
|
const unsigned long pathparts;
|
|
|
|
// The prefix for cloaks (e.g. MyNet).
|
|
const std::string prefix;
|
|
|
|
#ifdef HAS_LIBPSL
|
|
// Handle to the Public Suffix List library.
|
|
psl_ctx_t* psl;
|
|
#endif
|
|
|
|
// Dynamic reference to the sha256 implementation.
|
|
dynamic_reference_nocheck<HashProvider> sha256;
|
|
|
|
// The base32 table used when encoding.
|
|
const unsigned char* table;
|
|
|
|
// The suffix for IP cloaks (e.g. IP).
|
|
const std::string suffix;
|
|
|
|
std::string CloakAddress(const irc::sockets::sockaddrs& sa)
|
|
{
|
|
switch (sa.family())
|
|
{
|
|
case AF_INET:
|
|
return CloakIPv4(sa.in4.sin_addr.s_addr);
|
|
case AF_INET6:
|
|
return CloakIPv6(sa.in6.sin6_addr.s6_addr);
|
|
case AF_UNIX:
|
|
return CloakHost(sa.un.sun_path, '/', pathparts);
|
|
}
|
|
|
|
// Should never be reached.
|
|
return {};
|
|
}
|
|
|
|
std::string CloakIPv4(unsigned long address)
|
|
{
|
|
// IPv4 addresses are cloaked in the form ALPHA.BETA.GAMMA
|
|
//
|
|
// ALPHA is unique for a.b.c.d
|
|
// BETA is unique for a.b.c
|
|
// GAMMA is unique for a.b
|
|
unsigned int a = (unsigned int)(address) & 0xFF;
|
|
unsigned int b = (unsigned int)(address >> 8) & 0xFF;
|
|
unsigned int c = (unsigned int)(address >> 16) & 0xFF;
|
|
unsigned int d = (unsigned int)(address >> 24) & 0xFF;
|
|
|
|
const std::string alpha = Hash(FMT::format("{}.{}.{}.{}", a, b, c, d));
|
|
const std::string beta = Hash(FMT::format("{}.{}.{}", a, b, c));
|
|
const std::string gamma = Hash(FMT::format("{}.{}", a, b));
|
|
|
|
return Wrap(FMT::format("{}.{}.{}", alpha, beta, gamma), suffix, '.');
|
|
}
|
|
|
|
std::string CloakIPv6(const unsigned char* address)
|
|
{
|
|
// IPv6 addresses are cloaked in the form ALPHA.BETA.GAMMA.IP
|
|
//
|
|
// ALPHA is unique for a:b:c:d:e:f:g:h
|
|
// BETA is unique for a:b:c:d:e:f:g
|
|
// GAMMA is unique for a:b:c:d
|
|
const uint16_t* address16 = reinterpret_cast<const uint16_t*>(address);
|
|
unsigned int a = ntohs(address16[0]);
|
|
unsigned int b = ntohs(address16[1]);
|
|
unsigned int c = ntohs(address16[2]);
|
|
unsigned int d = ntohs(address16[3]);
|
|
unsigned int e = ntohs(address16[4]);
|
|
unsigned int f = ntohs(address16[5]);
|
|
unsigned int g = ntohs(address16[6]);
|
|
unsigned int h = ntohs(address16[7]);
|
|
|
|
const std::string alpha = Hash(FMT::format("{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", a, b, c, d, e, f, g, h));
|
|
const std::string beta = Hash(FMT::format("{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", a, b, c, d, e, f, g));
|
|
const std::string gamma = Hash(FMT::format("{:x}:{:x}:{:x}:{:x}", a, b, c, d));
|
|
|
|
return Wrap(FMT::format("{}:{}:{}", alpha, beta, gamma), suffix, ':');
|
|
}
|
|
|
|
std::string CloakHost(const std::string& host, char separator, unsigned long parts)
|
|
{
|
|
// Attempt to divine the public part of the hostname.
|
|
std::string visiblepart;
|
|
#ifdef HAS_LIBPSL
|
|
if (psl && separator == '.')
|
|
{
|
|
// Attempt to look up the suffix with libpsl.
|
|
const char* publicsuffix = psl_registrable_domain(psl, host.c_str());
|
|
if (!publicsuffix || publicsuffix == host)
|
|
publicsuffix = psl_unregistrable_domain(psl, host.c_str());
|
|
if (publicsuffix && publicsuffix != host)
|
|
visiblepart = publicsuffix;
|
|
}
|
|
#endif
|
|
|
|
// If libpsl failed to find a suffix or wasn't available fall back.
|
|
if (visiblepart.empty() && parts)
|
|
visiblepart = Cloak::VisiblePart(host, parts, separator);
|
|
|
|
// Convert the host to lowercase to avoid ban evasion.
|
|
std::string lowerhost(host.length(), '\0');
|
|
std::transform(host.begin(), host.end(), lowerhost.begin(), ::tolower);
|
|
|
|
return Wrap(Hash(lowerhost), visiblepart, separator);
|
|
}
|
|
|
|
std::string Hash(const std::string& str)
|
|
{
|
|
std::string out;
|
|
for (const auto chr : sha256->hmac(key, str).substr(0, segmentlen))
|
|
out.push_back(table[chr & 0x1F]);
|
|
return out;
|
|
}
|
|
|
|
std::string Wrap(const std::string& cloak, const std::string& cloaksuffix, char separator)
|
|
{
|
|
std::string fullcloak;
|
|
if (!prefix.empty())
|
|
fullcloak.append(prefix).append(1, separator);
|
|
fullcloak.append(cloak);
|
|
if (!cloaksuffix.empty())
|
|
fullcloak.append(1, separator).append(cloaksuffix);
|
|
return fullcloak;
|
|
}
|
|
|
|
public:
|
|
SHA256Method(const Cloak::Engine* engine, const std::shared_ptr<ConfigTag>& tag, const std::string& k, psl_ctx_t* p, bool ch) ATTR_NOT_NULL(2)
|
|
: Cloak::Method(engine, tag)
|
|
, cloakhost(ch)
|
|
, hostparts(cloakhost ? tag->getNum<unsigned long>("hostparts", 3, 0, ServerInstance->Config->Limits.MaxHost / 2) : 0)
|
|
, key(k)
|
|
, pathparts(tag->getNum<unsigned long>("pathparts", 1, 0, ServerInstance->Config->Limits.MaxHost / 2))
|
|
, prefix(tag->getString("prefix"))
|
|
#ifdef HAS_LIBPSL
|
|
, psl(p)
|
|
#endif
|
|
, sha256(engine->creator, "hash/sha256")
|
|
, suffix(tag->getString("suffix", "ip"))
|
|
{
|
|
table = tag->getEnum("case", base32lower, {
|
|
{ "upper", base32upper },
|
|
{ "lower", base32lower }
|
|
});
|
|
}
|
|
|
|
#ifdef HAS_PSL
|
|
~SHA256Method() override
|
|
{
|
|
if (psl)
|
|
psl_free(psl);
|
|
}
|
|
#endif
|
|
|
|
std::string Generate(LocalUser* user) override ATTR_NOT_NULL(2)
|
|
{
|
|
if (!sha256 || !MatchesUser(user))
|
|
return {};
|
|
|
|
irc::sockets::sockaddrs sa(false);
|
|
if (!cloakhost || (sa.from(user->GetRealHost()) && sa.addr() == user->client_sa.addr()))
|
|
return CloakAddress(user->client_sa);
|
|
|
|
return CloakHost(user->GetRealHost(), '.', hostparts);
|
|
}
|
|
|
|
std::string Generate(const std::string& hostip) override
|
|
{
|
|
if (!sha256)
|
|
return {};
|
|
|
|
irc::sockets::sockaddrs sa(false);
|
|
if (sa.from(hostip))
|
|
return CloakAddress(sa);
|
|
|
|
if (cloakhost)
|
|
return CloakHost(hostip, '.', hostparts);
|
|
|
|
return {}; // Only reachable on hmac-sha256-ip.
|
|
}
|
|
|
|
void GetLinkData(Module::LinkData& data) override
|
|
{
|
|
// The value we use for cloaks when the sha2 module is missing.
|
|
const std::string broken = "missing-sha2-module";
|
|
|
|
// IMPORTANT: link data is sent over unauthenticated server links so we
|
|
// can't directly send the key here. Instead we use dummy cloaks that
|
|
// allow verification of or less the same thing.
|
|
data["cloak-v4"] = sha256 ? Generate("123.123.123.123") : broken;
|
|
data["cloak-v6"] = sha256 ? Generate("dead:beef:cafe::") : broken;
|
|
data["cloak-unix"] = sha256 ? Generate("/extremely/long/inspircd/cloak.example") : broken;
|
|
data["path-parts"] = ConvToStr(pathparts);
|
|
data["prefix"] = prefix;
|
|
data["suffix"] = suffix;
|
|
|
|
if (!cloakhost)
|
|
return;
|
|
|
|
data["cloak-host"] = sha256 ? Generate("extremely.long.inspircd.cloak.example") : broken;
|
|
data["host-parts"] = ConvToStr(hostparts);
|
|
|
|
#ifdef HAS_PSL
|
|
data["using-psl"] = psl ? "yes" : "no";
|
|
#else
|
|
data["using-psl"] = "no";
|
|
#endif
|
|
}
|
|
|
|
bool IsLinkSensitive() const override
|
|
{
|
|
// This method always wants to be at the front.
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class SHA256Engine final
|
|
: public Cloak::Engine
|
|
{
|
|
private:
|
|
// The minimum length of a cloak key.
|
|
static constexpr size_t minkeylen = 30;
|
|
|
|
// Whether to cloak the hostname if available.
|
|
const bool cloakhost;
|
|
|
|
// Dynamic reference to the sha256 implementation.
|
|
dynamic_reference_nocheck<HashProvider> sha256;
|
|
|
|
public:
|
|
SHA256Engine(Module* Creator, const std::string& Name, bool ch)
|
|
: Cloak::Engine(Creator, Name)
|
|
, cloakhost(ch)
|
|
, sha256(Creator, "hash/sha256")
|
|
{
|
|
}
|
|
|
|
Cloak::MethodPtr Create(const std::shared_ptr<ConfigTag>& tag, bool primary) override
|
|
{
|
|
if (!sha256)
|
|
throw ModuleException(creator, "Unable to create a " + name.substr(6) + " cloak without the sha2 module, at" + tag->source.str());
|
|
|
|
// Ensure that we have the <cloak:key> parameter.
|
|
const std::string key = tag->getString("key");
|
|
if (key.length() < minkeylen)
|
|
throw ModuleException(creator, "Your cloak key should be at least " + ConvToStr(minkeylen) + " characters long, at " + tag->source.str());
|
|
|
|
psl_ctx_t* psl = nullptr;
|
|
std::string psldb = tag->getString("psl");
|
|
if (cloakhost && !psldb.empty())
|
|
{
|
|
#ifdef HAS_LIBPSL
|
|
if (insp::equalsci(psldb, "system"))
|
|
{
|
|
psldb = psl_dist_filename();
|
|
if (psldb.empty())
|
|
throw ModuleException(creator, "You specified \"system\" in <cloak:psl> but libpsl was built without a system copy, at " + tag->source.str());
|
|
}
|
|
|
|
psl = psl_load_file(psldb.c_str());
|
|
if (!psl)
|
|
throw ModuleException(creator, "The database specified in <cloak:psl> (" + psldb + ") does not exist, at " + tag->source.str());
|
|
#else
|
|
throw ModuleException(creator, "You specified <cloak:psl> but InspIRCd was built without libpsl, at " + tag->source.str());
|
|
#endif
|
|
}
|
|
|
|
return std::make_shared<SHA256Method>(this, tag, key, psl, cloakhost);
|
|
}
|
|
};
|
|
|
|
class ModuleCloakSHA256 final
|
|
: public Module
|
|
{
|
|
private:
|
|
SHA256Engine addrcloak;
|
|
SHA256Engine hostcloak;
|
|
|
|
public:
|
|
ModuleCloakSHA256()
|
|
: Module(VF_VENDOR, "Adds the hmac-sha256 and hmac-sha256-addr cloaking methods for use with the cloak module.")
|
|
, addrcloak(this, "hmac-sha256-addr", false)
|
|
, hostcloak(this, "hmac-sha256", true)
|
|
{
|
|
}
|
|
|
|
#ifdef HAS_LIBPSL
|
|
void init() override
|
|
{
|
|
ServerInstance->Logs.Normal(MODNAME, "Module was compiled against libpsl version {} and is running against version {}",
|
|
PSL_VERSION, psl_get_version());
|
|
}
|
|
#endif
|
|
};
|
|
|
|
MODULE_INIT(ModuleCloakSHA256)
|