aboutsummaryrefslogtreecommitdiffstats
path: root/src/modules
diff options
context:
space:
mode:
authorGravatar Sadie Powell2023-01-13 05:04:42 +0000
committerGravatar Sadie Powell2023-01-13 07:01:03 +0000
commit32258f5b11def1967e8c7c78ffdb7113d002df03 (patch)
tree746aca12f549eacad3b3ec9ab80aadd2f6d88d22 /src/modules
parentRename the old cloaking implementation to cloak_md5. (diff)
Convert the cloak_md5 module to the new cloak system.
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/m_cloak_md5.cpp454
-rw-r--r--src/modules/m_spanningtree/capab.cpp4
-rw-r--r--src/modules/m_spanningtree/main.cpp2
3 files changed, 111 insertions, 349 deletions
diff --git a/src/modules/m_cloak_md5.cpp b/src/modules/m_cloak_md5.cpp
index 098f29d2e..9eca2189b 100644
--- a/src/modules/m_cloak_md5.cpp
+++ b/src/modules/m_cloak_md5.cpp
@@ -32,8 +32,7 @@
#include "inspircd.h"
-#include "clientprotocolevent.h"
-#include "extension.h"
+#include "modules/cloak.h"
#include "modules/hash.h"
enum CloakMode
@@ -52,6 +51,7 @@ static constexpr char base32[] = "0123456789abcdefghijklmnopqrstuv";
static constexpr size_t minkeylen = 30;
struct CloakInfo final
+ : public Cloak::Method
{
// The method used for cloaking users.
CloakMode mode;
@@ -71,206 +71,34 @@ struct CloakInfo final
// The suffix for IP cloaks (e.g. .IP).
std::string suffix;
- CloakInfo(CloakMode Mode, const std::string& Key, const std::string& Prefix, const std::string& Suffix, bool IgnoreCase, unsigned int DomainParts = 0)
- : mode(Mode)
+ dynamic_reference<HashProvider> Hash;
+
+ CloakInfo(Cloak::Engine* engine, CloakMode Mode, const std::string& Key, const std::string& Prefix, const std::string& Suffix, bool IgnoreCase, unsigned int DomainParts = 0)
+ : Cloak::Method(engine)
+ , mode(Mode)
, domainparts(DomainParts)
, ignorecase(IgnoreCase)
, key(Key)
, prefix(Prefix)
, suffix(Suffix)
+ , Hash(engine->creator, "hash/md5")
{
}
-};
-
-typedef std::vector<std::string> CloakList;
-
-class CloakUser final
- : public ModeHandler
-{
-public:
- bool active = false;
- ListExtItem<CloakList> ext;
- std::string debounce_uid;
- time_t debounce_ts = 0;
- int debounce_count = 0;
-
- CloakUser(Module* source)
- : ModeHandler(source, "cloak", 'x', PARAM_NONE, MODETYPE_USER)
- , ext(source, "cloaks", ExtensionType::USER)
- {
- }
-
- ModeAction OnModeChange(User* source, User* dest, Channel* channel, Modes::Change& change) override
- {
- LocalUser* user = IS_LOCAL(dest);
- /* For remote clients, we don't take any action, we just allow it.
- * The local server where they are will set their cloak instead.
- * This is fine, as we will receive it later.
- */
- if (!user)
- {
- // Remote setters broadcast mode before host while local setters do the opposite, so this takes that into account
- active = IS_LOCAL(source) ? change.adding : !change.adding;
- dest->SetMode(this, change.adding);
- return MODEACTION_ALLOW;
- }
-
- if (user->uuid == debounce_uid && debounce_ts == ServerInstance->Time())
- {
- // prevent spamming using /mode user +x-x+x-x+x-x
- if (++debounce_count > 2)
- return MODEACTION_DENY;
- }
- else
- {
- debounce_uid = user->uuid;
- debounce_count = 1;
- debounce_ts = ServerInstance->Time();
- }
-
- if (change.adding == user->IsModeSet(this))
- return MODEACTION_DENY;
-
- /* don't allow this user to spam modechanges */
- if (source == dest)
- user->CommandFloodPenalty += 5000;
-
- if (change.adding)
- {
- // assume this is more correct
- if (!user->IsFullyConnected() && user->GetRealHost() != user->GetDisplayedHost())
- return MODEACTION_DENY;
-
- CloakList* cloaks = ext.Get(user);
- if (!cloaks)
- {
- /* Force creation of missing cloak */
- try
- {
- creator->OnUserConnect(user);
- cloaks = ext.Get(user);
- }
- catch (const CoreException& modexcept)
- {
- ServerInstance->Logs.Normal(MODNAME, "Exception caught when generating cloak: " + modexcept.GetReason());
- return MODEACTION_DENY;
- }
- }
-
- // If we have a cloak then set the hostname.
- if (cloaks && !cloaks->empty())
- {
- user->ChangeDisplayedHost(cloaks->front());
- user->SetMode(this, true);
- return MODEACTION_ALLOW;
- }
- else
- return MODEACTION_DENY;
- }
- else
- {
- /* User is removing the mode, so restore their real host
- * and make it match the displayed one.
- */
- user->SetMode(this, false);
- user->ChangeDisplayedHost(user->GetRealHost());
- return MODEACTION_ALLOW;
- }
- }
-};
-
-class CommandCloak final
- : public Command
-{
-public:
- CommandCloak(Module* Creator)
- : Command(Creator, "CLOAK", 1)
- {
- access_needed = CmdAccess::OPERATOR;
- syntax = { "<host>" };
- }
-
- CmdResult Handle(User* user, const Params& parameters) override;
-};
-
-class ModuleCloaking final
- : public Module
-{
-public:
- CloakUser cu;
- CommandCloak ck;
- std::vector<CloakInfo> cloaks;
- dynamic_reference<HashProvider> Hash;
-
- ModuleCloaking()
- : Module(VF_VENDOR | VF_COMMON, "Adds user mode x (cloak) which allows user hostnames to be hidden.")
- , cu(this)
- , ck(this)
- , Hash(this, "hash/md5")
- {
- }
-
- /** Takes a domain name and retrieves the subdomain which should be visible.
- * This is usually the last \p domainparts labels but if not enough are
- * present then all but the most specific label are used. If the domain name
- * consists of one label only then none are used.
- *
- * Here are some examples for how domain names will be shortened assuming
- * \p domainparts is set to the default of 3.
- *
- * "this.is.an.example.com" => ".an.example.com"
- * "an.example.com" => ".example.com"
- * "example.com" => ".com"
- * "localhost" => ""
- *
- * @param host The hostname to cloak.
- * @param domainparts The number of domain labels that should be visible.
- * @return The visible segment of the hostname.
- */
- static std::string VisibleDomainParts(const std::string& host, unsigned int domainparts)
- {
- // The position at which we found the last dot.
- std::string::const_reverse_iterator dotpos;
-
- // The number of dots we have seen so far.
- unsigned int seendots = 0;
-
- for (std::string::const_reverse_iterator iter = host.rbegin(); iter != host.rend(); ++iter)
- {
- if (*iter != '.')
- continue;
-
- // We have found a dot!
- dotpos = iter;
- seendots += 1;
-
- // Do we have enough segments to stop?
- if (seendots >= domainparts)
- break;
- }
-
- // We only returns a domain part if more than one label is
- // present. See above for a full explanation.
- if (!seendots)
- return "";
- return std::string(dotpos.base() - 1, host.end());
- }
-
- /**
+ /*
* 2.0-style cloaking function
* @param item The item to cloak (part of an IP or hostname)
* @param id A unique ID for this type of item (to make it unique if the item matches)
* @param len The length of the output. Maximum for MD5 is 16 characters.
*/
- std::string SegmentCloak(const CloakInfo& info, const std::string& item, char id, size_t len)
+ std::string SegmentCloak(const std::string& item, char id, size_t len)
{
std::string input;
- input.reserve(info.key.length() + 3 + item.length());
+ input.reserve(key.length() + 3 + item.length());
input.append(1, id);
- input.append(info.key);
+ input.append(key);
input.append(1, '\0'); // null does not terminate a C++ string
- if (info.ignorecase)
+ if (ignorecase)
std::transform(item.begin(), item.end(), std::back_inserter(input), ::tolower);
else
input.append(item);
@@ -286,7 +114,7 @@ public:
return rv;
}
- std::string SegmentIP(const CloakInfo& info, const irc::sockets::sockaddrs& ip, bool full)
+ std::string SegmentIP(const irc::sockets::sockaddrs& ip, bool full)
{
std::string bindata;
size_t hop1;
@@ -305,7 +133,7 @@ public:
len2 = 4;
// pfx s1.s2.s3. (xxxx.xxxx or s4) sfx
// 6 4 4 9/6
- rv.reserve(info.prefix.length() + 26 + info.suffix.length());
+ rv.reserve(prefix.length() + 26 + suffix.length());
}
else
{
@@ -315,27 +143,27 @@ public:
hop3 = 2;
len1 = len2 = 3;
// pfx s1.s2. (xxx.xxx or s3) sfx
- rv.reserve(info.prefix.length() + 15 + info.suffix.length());
+ rv.reserve(prefix.length() + 15 + suffix.length());
}
- rv.append(info.prefix);
- rv.append(SegmentCloak(info, bindata, 10, len1));
+ rv.append(prefix);
+ rv.append(SegmentCloak(bindata, 10, len1));
rv.append(1, '.');
bindata.erase(hop1);
- rv.append(SegmentCloak(info, bindata, 11, len2));
+ rv.append(SegmentCloak(bindata, 11, len2));
if (hop2)
{
rv.append(1, '.');
bindata.erase(hop2);
- rv.append(SegmentCloak(info, bindata, 12, len2));
+ rv.append(SegmentCloak(bindata, 12, len2));
}
if (full)
{
rv.append(1, '.');
bindata.erase(hop3);
- rv.append(SegmentCloak(info, bindata, 13, 6));
- rv.append(info.suffix);
+ rv.append(SegmentCloak(bindata, 13, 6));
+ rv.append(suffix);
}
else
{
@@ -343,102 +171,45 @@ public:
{
rv.append(InspIRCd::Format(".%02x%02x.%02x%02x%s",
ip.in6.sin6_addr.s6_addr[2], ip.in6.sin6_addr.s6_addr[3],
- ip.in6.sin6_addr.s6_addr[0], ip.in6.sin6_addr.s6_addr[1], info.suffix.c_str()));
+ ip.in6.sin6_addr.s6_addr[0], ip.in6.sin6_addr.s6_addr[1], suffix.c_str()));
}
else
{
const unsigned char* ip4 = (const unsigned char*)&ip.in4.sin_addr;
- rv.append(InspIRCd::Format(".%d.%d%s", ip4[1], ip4[0], info.suffix.c_str()));
+ rv.append(InspIRCd::Format(".%d.%d%s", ip4[1], ip4[0], suffix.c_str()));
}
}
return rv;
}
- ModResult OnCheckBan(User* user, Channel* chan, const std::string& mask) override
- {
- LocalUser* lu = IS_LOCAL(user);
- if (!lu)
- return MOD_RES_PASSTHRU;
-
- // Force the creation of cloaks if not already set.
- OnUserConnect(lu);
-
- // If the user has no cloaks (i.e. UNIX socket) then we do nothing here.
- CloakList* cloaklist = cu.ext.Get(user);
- if (!cloaklist || cloaklist->empty())
- return MOD_RES_PASSTHRU;
-
- // Check if they have a cloaked host but are not using it.
- for (const auto& cloak : *cloaklist)
- {
- if (cloak != user->GetDisplayedHost())
- {
- const std::string cloakMask = user->nick + "!" + user->ident + "@" + cloak;
- if (InspIRCd::Match(cloakMask, mask))
- return MOD_RES_DENY;
- }
- }
- return MOD_RES_PASSTHRU;
- }
-
- void Prioritize() override
- {
- /* Needs to be after m_banexception etc. */
- ServerInstance->Modules.SetPriority(this, I_OnCheckBan, PRIORITY_LAST);
- }
-
- // this unsets umode +x on every host change. If we are actually doing a +x
- // mode change, we will call SetMode back to true AFTER the host change is done.
- void OnChangeHost(User* u, const std::string& host) override
- {
- if (u->IsModeSet(cu) && !cu.active)
- {
- u->SetMode(cu, false);
-
- LocalUser* luser = IS_LOCAL(u);
- if (!luser)
- return;
-
- Modes::ChangeList modechangelist;
- modechangelist.push_remove(&cu);
- ClientProtocol::Events::Mode modeevent(ServerInstance->FakeClient, nullptr, u, modechangelist);
- luser->Send(modeevent);
- }
- cu.active = false;
- }
-
- std::string GetCompatLinkData(const CloakInfo& info)
+ std::string GetCompatLinkData()
{
std::string data = "broken";
if (Hash)
{
- switch (info.mode)
+ switch (mode)
{
case MODE_HALF_CLOAK:
// Use old cloaking verification to stay compatible with 2.0
// But verify domainparts and ignorecase when use 3.0-only features
- if (info.domainparts == 3 && !info.ignorecase)
- data = info.prefix + SegmentCloak(info, "*", 3, 8) + info.suffix;
+ if (domainparts == 3 && !ignorecase)
+ data = prefix + SegmentCloak("*", 3, 8) + suffix;
else
{
irc::sockets::sockaddrs sa;
- data = GenCloak(info, sa, "", data + ConvToStr(info.domainparts)) + (info.ignorecase ? "-ci" : "");
+ data = GenCloak(sa, "", data + ConvToStr(domainparts)) + (ignorecase ? "-ci" : "");
}
break;
case MODE_OPAQUE:
- data = info.prefix + SegmentCloak(info, "*", 4, 8) + info.suffix + (info.ignorecase ? "-ci" : "");
+ data = prefix + SegmentCloak("*", 4, 8) + suffix + (ignorecase ? "-ci" : "");
}
}
return data;
}
- void GetLinkData(LinkData& data, std::string& compatdata) override
+ void GetLinkData(Module::LinkData& data, std::string& compatdata) override
{
- if (cloaks.empty())
- return;
-
- const CloakInfo& info = cloaks.front();
- switch (info.mode)
+ switch (mode)
{
case MODE_HALF_CLOAK:
data["mode"] = "half";
@@ -447,127 +218,116 @@ public:
data["mode"] = "full";
break;
}
- data["domain-parts"] = ConvToStr(info.domainparts);
+ data["domain-parts"] = ConvToStr(domainparts);
data["hash"] = Hash ? Hash->name : "broken";
- data["ignore-case"] = info.ignorecase ? "yes" : "no";
- data["key"] = info.key;
- data["prefix"] = info.prefix;
- data["suffix"] = info.suffix;
- compatdata = GetCompatLinkData(info);
- }
-
- void ReadConfig(ConfigStatus& status) override
- {
- auto tags = ServerInstance->Config->ConfTags("cloak");
- if (tags.empty())
- throw ModuleException(this, "You have loaded the cloaking module but not configured any <cloak> tags!");
-
- bool firstcloak = true;
- std::vector<CloakInfo> newcloaks;
- for (const auto& [_, tag] : tags)
- {
- // Ensure that we have the <cloak:key> parameter.
- const std::string key = tag->getString("key");
- if (key.empty())
- throw ModuleException(this, "You have not defined a cloaking key. Define <cloak:key> as a " + ConvToStr(minkeylen) + "+ character network-wide secret, at " + tag->source.str());
-
- // If we are the first cloak method then mandate a strong key.
- if (firstcloak && key.length() < minkeylen)
- throw ModuleException(this, "Your cloaking key is not secure. It should be at least " + ConvToStr(minkeylen) + " characters long, at " + tag->source.str());
-
- firstcloak = false;
- const bool ignorecase = tag->getBool("ignorecase");
- const std::string mode = tag->getString("mode");
- const std::string prefix = tag->getString("prefix");
- const std::string suffix = tag->getString("suffix", ".IP");
- if (stdalgo::string::equalsci(mode, "half"))
- {
- unsigned int domainparts = static_cast<unsigned int>(tag->getUInt("domainparts", 3, 1, 10));
- newcloaks.emplace_back(MODE_HALF_CLOAK, key, prefix, suffix, ignorecase, domainparts);
- }
- else if (stdalgo::string::equalsci(mode, "full"))
- newcloaks.emplace_back(MODE_OPAQUE, key, prefix, suffix, ignorecase);
- else
- throw ModuleException(this, mode + " is an invalid value for <cloak:mode>; acceptable values are 'half' and 'full', at " + tag->source.str());
- }
-
- // The cloak configuration was valid so we can apply it.
- cloaks.swap(newcloaks);
+ data["ignore-case"] = ignorecase ? "yes" : "no";
+ data["key"] = key;
+ data["prefix"] = prefix;
+ data["suffix"] = suffix;
+ compatdata = GetCompatLinkData();
}
- std::string GenCloak(const CloakInfo& info, const irc::sockets::sockaddrs& ip, const std::string& ipstr, const std::string& host)
+ std::string GenCloak(const irc::sockets::sockaddrs& ip, const std::string& ipstr, const std::string& host)
{
std::string chost;
irc::sockets::sockaddrs hostip(false);
bool host_is_ip = hostip.from_ip_port(host, ip.port()) && hostip == ip;
- switch (info.mode)
+ switch (mode)
{
case MODE_HALF_CLOAK:
{
if (!host_is_ip)
- chost = info.prefix + SegmentCloak(info, host, 1, 6) + VisibleDomainParts(host, info.domainparts);
+ chost = prefix + SegmentCloak(host, 1, 6) + "." + Cloak::VisiblePart(host, domainparts, '.');
if (chost.empty() || chost.length() > 50)
- chost = SegmentIP(info, ip, false);
+ chost = SegmentIP(ip, false);
break;
}
case MODE_OPAQUE:
- chost = SegmentIP(info, ip, true);
+ chost = SegmentIP(ip, true);
break;
}
return chost;
}
- void OnChangeRemoteAddress(LocalUser* user) override
+ std::string Generate(LocalUser* user) override ATTR_NOT_NULL(2)
{
- // Connecting users are handled in OnUserConnect not here.
- if (!user->IsFullyConnected() || user->quitting)
- return;
+ if (!Hash)
+ return {};
- // Remove the cloaks and generate new ones.
- cu.ext.Unset(user);
- OnUserConnect(user);
+ if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6)
+ return {};
- // If a user is using a cloak then update it.
- if (user->IsModeSet(cu))
- {
- CloakList* cloaklist = cu.ext.Get(user);
- user->ChangeDisplayedHost(cloaklist->front());
- }
+ return GenCloak(user->client_sa, user->GetIPString(), user->GetRealHost());
}
- void OnUserConnect(LocalUser* dest) override
+ std::string Generate(const std::string& hostip) override
{
- if (cu.ext.Get(dest))
- return;
-
- // TODO: decide how we are going to cloak AF_UNIX hostnames.
- if (dest->client_sa.family() != AF_INET && dest->client_sa.family() != AF_INET6)
- return;
+ if (!Hash)
+ return {};
- CloakList cloaklist;
- for (const auto& cloak : cloaks)
- cloaklist.push_back(GenCloak(cloak, dest->client_sa, dest->GetIPString(), dest->GetRealHost()));
- cu.ext.Set(dest, cloaklist);
+ irc::sockets::sockaddrs sa;
+ const char* ipaddr = sa.from_ip(hostip) ? hostip.c_str() : "";
+ return GenCloak(sa, ipaddr, hostip);
}
};
-CmdResult CommandCloak::Handle(User* user, const Params& parameters)
+class MD5Engine final
+ : public Cloak::Engine
{
- ModuleCloaking* mod = (ModuleCloaking*)(Module*)creator;
+private:
+ bool halfcloak;
+
+public:
+ MD5Engine(Module* Creator, const std::string& Name, bool hc)
+ : Cloak::Engine(Creator, Name)
+ , halfcloak(hc)
+ {
+ }
+
+ Cloak::MethodPtr Create(const std::shared_ptr<ConfigTag>& tag, bool primary) override
+ {
+ // Ensure that we have the <cloak:key> parameter.
+ const std::string key = tag->getString("key");
+ if (key.empty())
+ throw ModuleException(creator, "You have not defined a cloaking key. Define <cloak:key> as a " + ConvToStr(minkeylen) + "+ character network-wide secret, at " + tag->source.str());
+
+ // If we are the first cloak method then mandate a strong key.
+ if (primary && key.length() < minkeylen)
+ throw ModuleException(creator, "Your cloaking key is not secure. It should be at least " + ConvToStr(minkeylen) + " characters long, at " + tag->source.str());
- // If we're cloaking an IP address we pass it in the IP field too.
- irc::sockets::sockaddrs sa;
- const char* ipaddr = sa.from_ip(parameters[0]) ? parameters[0].c_str() : "";
+ const bool ignorecase = tag->getBool("ignorecase");
+ const std::string mode = tag->getString("mode");
+ const std::string prefix = tag->getString("prefix");
+ const std::string suffix = tag->getString("suffix", ".IP");
- unsigned int id = 0;
- for (const auto& info : mod->cloaks)
+ if (halfcloak)
+ {
+ unsigned int domainparts = static_cast<unsigned int>(tag->getUInt("domainparts", 3, 1, 10));
+ return std::make_shared<CloakInfo>(this, MODE_HALF_CLOAK, key, prefix, suffix, ignorecase, domainparts);
+ }
+ else
+ {
+ return std::make_shared<CloakInfo>(this, MODE_OPAQUE, key, prefix, suffix, ignorecase);
+ }
+ }
+};
+
+class ModuleCloakMD5 final
+ : public Module
+{
+private:
+ MD5Engine halfcloakengine;
+ MD5Engine fullcloakengine;
+
+public:
+ ModuleCloakMD5()
+ : Module(VF_VENDOR, "Provides the half and full cloak engines.")
+ , halfcloakengine(this, "half", true)
+ , fullcloakengine(this, "full", false)
{
- const std::string cloak = mod->GenCloak(info, sa, ipaddr, parameters[0]);
- user->WriteNotice(InspIRCd::Format("*** Cloak #%u for %s is %s", ++id, parameters[0].c_str(), cloak.c_str()));
}
- return CmdResult::SUCCESS;
-}
+};
-MODULE_INIT(ModuleCloaking)
+MODULE_INIT(ModuleCloakMD5)
diff --git a/src/modules/m_spanningtree/capab.cpp b/src/modules/m_spanningtree/capab.cpp
index dee87c4e5..14e90f987 100644
--- a/src/modules/m_spanningtree/capab.cpp
+++ b/src/modules/m_spanningtree/capab.cpp
@@ -57,7 +57,9 @@ namespace
modname.append(name.substr(0, endpos)).append(".so");
// Handle renamed modules.
- if (stdalgo::string::equalsci(modname, "m_realnameban.so"))
+ if (stdalgo::string::equalsci(modname, "m_cloak.so"))
+ modname = "m_cloaking.so";
+ else if (stdalgo::string::equalsci(modname, "m_realnameban.so"))
modname = "m_gecosban.so";
else if (stdalgo::string::equalsci(modname, "m_account.so") && ServerInstance->Modules.Find("services"))
modname = "m_services_account.so";
diff --git a/src/modules/m_spanningtree/main.cpp b/src/modules/m_spanningtree/main.cpp
index 365994715..6f517afa2 100644
--- a/src/modules/m_spanningtree/main.cpp
+++ b/src/modules/m_spanningtree/main.cpp
@@ -878,7 +878,7 @@ ModuleSpanningTree::~ModuleSpanningTree()
/* It is IMPORTANT that m_spanningtree is the last module in the chain
* so that any activity it sees is FINAL, e.g. we arent going to send out
- * a NICK message before m_cloaking has finished putting the +x on the user,
+ * a NICK message before the cloak module has finished putting the +x on the user,
* etc etc.
* Therefore, we set our priority to PRIORITY_LAST to make sure we end up at the END of
* the module call queue.