aboutsummaryrefslogtreecommitdiffstats
path: root/src/modules/m_codepage.cpp
diff options
context:
space:
mode:
authorGravatar Sadie Powell2024-07-17 00:06:50 +0100
committerGravatar Sadie Powell2024-07-17 00:06:50 +0100
commit889d521e05f2e922683d48c74eb00290e7a2f548 (patch)
treeb4c56bd4cdf0881601c7e4d6ff571e491af10bf4 /src/modules/m_codepage.cpp
parentUse std::endian from C++20 in the sha1 module. (diff)
downloadinspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.tar.gz
inspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.tar.bz2
inspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.zip
Shuffle the modules about a bit.
Diffstat (limited to 'src/modules/m_codepage.cpp')
-rw-r--r--src/modules/m_codepage.cpp446
1 files changed, 0 insertions, 446 deletions
diff --git a/src/modules/m_codepage.cpp b/src/modules/m_codepage.cpp
deleted file mode 100644
index fe6aafad1..000000000
--- a/src/modules/m_codepage.cpp
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * InspIRCd -- Internet Relay Chat Daemon
- *
- * Copyright (C) 2020-2023 Sadie Powell <sadie@witchery.services>
- *
- * 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/>.
- */
-
-
-#include "inspircd.h"
-#include "modules/isupport.h"
-
-class Codepage
-{
-public:
- enum class AllowCharacterResult
- : uint8_t
- {
- // The character is allowed in a nick.
- OKAY,
-
- // The character is never valid in a nick.
- NOT_VALID,
-
- // The character is not valid at the front of a nick.
- NOT_VALID_AT_FRONT,
- };
-
- // The mapping of lower case characters to upper case characters.
- unsigned char casemap[UCHAR_MAX + 1];
-
- // Initialises the Codepage class.
- Codepage()
- {
- for (size_t i = 0; i <= UCHAR_MAX; ++i)
- casemap[i] = i;
- }
-
- // Destroys the Codepage class.
- virtual ~Codepage() = default;
-
- // Specifies that a character is allowed.
- virtual AllowCharacterResult AllowCharacter(uint32_t character, bool front)
- {
- if (front)
- {
- // Nicknames can not begin with a number as that would collide with
- // a user identifier.
- if (character >= '0' && character <= '9')
- return AllowCharacterResult::NOT_VALID_AT_FRONT;
-
- // Nicknames can not begin with a : or a $ as they have a special
- // meaning within the IRC message format.
- if (character == '$' || character == ':')
- return AllowCharacterResult::NOT_VALID_AT_FRONT;
-
- // Nicknames can not begin with a channel prefix character (e.g. #)
- // as they are used to differentiate users from channels in message
- // targets.
- if (ServerInstance->Channels.IsPrefix(character))
- return AllowCharacterResult::NOT_VALID_AT_FRONT;
-
- // Nicknames can not begin with a prefix mode character (e.g. @)
- // as they are used as a nick prefix when sending a STATUSMSG.
- if (ServerInstance->Modes.FindPrefix(character))
- return AllowCharacterResult::NOT_VALID_AT_FRONT;
- }
-
- // Nicknames can never contain NUL, CR, LF, SPACE, COMMA, PERIOD,
- // ASTERISK, QUESTION MARK, EXCLAMATION MARK, or AT SIGN as they are
- // either banned within an IRC message or have special meaning within
- // the IRC message format.
- switch (character)
- {
- case '\0':
- case '\n':
- case '\r':
- case ' ':
- case '!':
- case '*':
- case ',':
- case '.':
- case '?':
- case '@':
- return AllowCharacterResult::NOT_VALID;
- }
-
-
- // The character is probably okay?
- return AllowCharacterResult::OKAY;
- }
-
- // Determines whether a nickname is valid.
- virtual bool IsValidNick(const std::string_view& nick) = 0;
-
- // Retrieves the link data for this codepage.
- virtual void GetLinkData(Module::LinkData& data, std::string& compatdata) const = 0;
-
- // Maps an upper case character to a lower case character.
- virtual bool Map(uint32_t upper, uint32_t lower) = 0;
-};
-
-class SingleByteCodepage final
- : public Codepage
-{
-private:
- // The characters which are allowed in nicknames.
- CharState allowedchars;
-
- // The characters which are allowed at the front of a nickname.
- CharState allowedfrontchars;
-
-public:
- AllowCharacterResult AllowCharacter(uint32_t character, bool front) override
- {
- // Single byte codepage can, as their name suggests, only be one byte in size.
- if (character > UCHAR_MAX)
- return AllowCharacterResult::NOT_VALID;
-
- // Check the common allowed character rules.
- AllowCharacterResult result = Codepage::AllowCharacter(character, front);
- if (result != AllowCharacterResult::OKAY)
- return result;
-
- // The character is okay.
- allowedchars.set(character);
- allowedfrontchars.set(character, front);
- return AllowCharacterResult::OKAY;
- }
-
- bool IsValidNick(const std::string_view& nick) override
- {
- if (nick.empty() || nick.length() > ServerInstance->Config->Limits.MaxNick)
- return false;
-
- for (std::string_view::const_iterator iter = nick.begin(); iter != nick.end(); ++iter)
- {
- unsigned char chr = static_cast<unsigned char>(*iter);
-
- // Check that the character is allowed at the front of the nick.
- if (iter == nick.begin() && !allowedfrontchars[chr])
- return false;
-
- // Check that the character is allowed in the nick.
- if (!allowedchars[chr])
- return false;
- }
-
- return true;
- }
-
- void GetLinkData(Module::LinkData& data, std::string& compatdata) const override
- {
- for (size_t i = 0; i < allowedfrontchars.size(); ++i)
- if (allowedfrontchars[i])
- data["front"].push_back(static_cast<unsigned char>(i));
-
- for (size_t i = 0; i < allowedchars.size(); ++i)
- if (allowedchars[i])
- data["middle"].push_back(static_cast<unsigned char>(i));
-
- for (size_t i = 0; i < sizeof(casemap); ++i)
- {
- if (casemap[i] == i)
- continue;
-
- data["map"].push_back(static_cast<unsigned char>(i));
- data["map"].push_back(casemap[i]);
- data["map"].push_back(',');
- }
-
- compatdata = fmt::format("front={}&middle={}&map={}", data["front"], data["middle"], data["map"]);
- }
-
- bool Map(uint32_t upper, uint32_t lower) override
- {
- if (upper > UCHAR_MAX || lower > UCHAR_MAX)
- return false;
-
- casemap[upper] = lower;
- return true;
- }
-};
-
-class ModuleCodepage final
- : public Module
- , public ISupport::EventListener
-{
-private:
- // The currently active codepage.
- std::unique_ptr<Codepage> codepage = nullptr;
-
- // The character map which was set before this module was loaded.
- const unsigned char* origcasemap;
-
- // The name of the character map which was set before this module was loaded.
- const std::string origcasemapname;
-
- // The IsNick handler which was set before this module was loaded.
- const std::function<bool(const std::string_view&)> origisnick;
-
- // The character set used for the codepage.
- std::string charset;
-
- template <typename T>
- static void RehashHashmap(T& hashmap)
- {
- T newhash(hashmap.bucket_count());
- for (const auto& [key, value] : hashmap)
- newhash.emplace(key, value);
- hashmap.swap(newhash);
- }
-
- static void DestroyChannel(Channel* chan)
- {
- // Remove all of the users from the channel. Using KICK here will mean
- // the user's client will probably attempt to rejoin and will enter the
- // succeeding channel. Unfortunately this is the best we can do for now.
- while (!chan->userlist.empty())
- chan->KickUser(ServerInstance->FakeClient, chan->userlist.begin(), "This channel does not exist anymore.");
-
- // Remove all modes from the channel just in case one of them keeps the channel open.
- Modes::ChangeList changelist;
- for (const auto& [_, mh] : ServerInstance->Modes.GetModes(MODETYPE_CHANNEL))
- mh->RemoveMode(chan, changelist);
- ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, nullptr, changelist, ModeParser::MODE_LOCALONLY);
-
- // The channel will be destroyed automatically by CheckDestroy.
- }
-
- static void ChangeNick(User* user, const std::string& message)
- {
- user->WriteNumeric(RPL_SAVENICK, user->uuid, message);
- user->ChangeNick(user->uuid);
- }
-
- static void CheckDuplicateChan()
- {
- ChannelMap duplicates;
- for (auto &[_, chan] : ServerInstance->Channels.GetChans())
- {
- auto check = duplicates.insert(std::make_pair(chan->name, chan));
- if (check.second)
- continue; // No duplicate.
-
- Channel* otherchan = check.first->second;
- if (otherchan->age < chan->age)
- {
- // The other channel was created first.
- DestroyChannel(chan);
- }
- else if (otherchan->age > chan->age)
- {
- // The other channel was created last.
- DestroyChannel(otherchan);
- check.first->second = chan;
- }
- else
- {
- // Both created at the same time.
- DestroyChannel(chan);
- DestroyChannel(otherchan);
- duplicates.erase(check.first);
- }
- }
- }
-
- static void CheckDuplicateNick()
- {
- UserMap duplicates;
- for (auto* user : ServerInstance->Users.GetLocalUsers())
- {
- if (user->nick == user->uuid)
- continue; // UUID users are always unique.
-
- auto check = duplicates.insert(std::make_pair(user->nick, user));
- if (check.second)
- continue; // No duplicate.
-
- User* otheruser = check.first->second;
- if (otheruser->nickchanged < user->nickchanged)
- {
- // The other user connected first.
- ChangeNick(user, "Your nickname is no longer available.");
- }
- else if (otheruser->nickchanged > user->nickchanged)
- {
- // The other user connected last.
- ChangeNick(otheruser, "Your nickname is no longer available.");
- check.first->second = user;
- }
- else
- {
- // Both connected at the same time.
- ChangeNick(user, "Your nickname is no longer available.");
- ChangeNick(otheruser, "Your nickname is no longer available.");
- duplicates.erase(check.first);
- }
- }
- }
-
- static void CheckInvalidNick()
- {
- for (auto* user : ServerInstance->Users.GetLocalUsers())
- {
- if (user->nick != user->uuid && !ServerInstance->IsNick(user->nick))
- ChangeNick(user, "Your nickname is no longer valid.");
- }
- }
-
- static void CheckRehash(unsigned char* prevmap)
- {
- if (!memcmp(prevmap, national_case_insensitive_map, UCHAR_MAX))
- return;
-
- RehashHashmap(ServerInstance->Users.clientlist);
- RehashHashmap(ServerInstance->Users.uuidlist);
- RehashHashmap(ServerInstance->Channels.GetChans());
- }
-
- static std::string GetPrintable(uint32_t chr)
- {
- if (isprint(chr))
- return fmt::format("{} ({})", reinterpret_cast<const char*>(&chr), chr);
- else
- return fmt::format("{}", chr);
- }
-
-public:
- ModuleCodepage()
- : Module(VF_VENDOR | VF_COMMON, "Allows the server administrator to define what characters are allowed in nicknames and how characters should be compared in a case insensitive way.")
- , ISupport::EventListener(this)
- , origcasemap(national_case_insensitive_map)
- , origcasemapname(ServerInstance->Config->CaseMapping)
- , origisnick(ServerInstance->IsNick)
- {
- }
-
- ~ModuleCodepage() override
- {
- ServerInstance->IsNick = origisnick;
- CheckInvalidNick();
-
- ServerInstance->Config->CaseMapping = origcasemapname;
- national_case_insensitive_map = origcasemap;
- CheckDuplicateChan();
- CheckDuplicateNick();
- if (codepage) // nullptr if ReadConfig throws on load.
- CheckRehash(codepage->casemap);
- }
-
- void ReadConfig(ConfigStatus& status) override
- {
- const auto& codepagetag = ServerInstance->Config->ConfValue("codepage");
-
- const std::string name = codepagetag->getString("name");
- if (name.empty())
- throw ModuleException(this, "<codepage:name> is a required field!");
-
- std::unique_ptr<Codepage> newcodepage = std::make_unique<SingleByteCodepage>();
- for (const auto& [_, tag] : ServerInstance->Config->ConfTags("cpchars"))
- {
- uint32_t begin = tag->getNum<uint32_t>("begin", tag->getNum<uint32_t>("index", 0));
- if (!begin)
- throw ModuleException(this, "<cpchars> tag without index or begin specified at " + tag->source.str());
-
- uint32_t end = tag->getNum<uint32_t>("end", begin);
- if (begin > end)
- throw ModuleException(this, "<cpchars:begin> must be lower than <cpchars:end> at " + tag->source.str());
-
- bool front = tag->getBool("front", false);
- for (uint32_t pos = begin; pos <= end; ++pos)
- {
- switch (newcodepage->AllowCharacter(pos, front))
- {
- case Codepage::AllowCharacterResult::OKAY:
- ServerInstance->Logs.Debug(MODNAME, "Marked {} as allowed (front: {})",
- GetPrintable(pos), front ? "yes" : "no");
- break;
-
- case Codepage::AllowCharacterResult::NOT_VALID:
- throw ModuleException(this, fmt::format("<cpchars> tag contains a forbidden character: {} at {}",
- GetPrintable(pos), tag->source.str()));
-
- case Codepage::AllowCharacterResult::NOT_VALID_AT_FRONT:
- throw ModuleException(this, fmt::format("<cpchars> tag contains a forbidden front character: {} at {}",
- GetPrintable(pos), tag->source.str()));
- }
- }
- }
-
- for (const auto& [_, tag] : ServerInstance->Config->ConfTags("cpcase"))
- {
- uint32_t lower = tag->getNum<uint32_t>("lower", 0);
- if (!lower)
- throw ModuleException(this, "<cpcase:lower> is required at " + tag->source.str());
-
- uint32_t upper = tag->getNum<uint32_t>("upper", 0);
- if (!upper)
- throw ModuleException(this, "<cpcase:upper> is required at " + tag->source.str());
-
- if (!newcodepage->Map(upper, lower))
- throw ModuleException(this, "Malformed <cpcase> tag at " + tag->source.str());
-
- ServerInstance->Logs.Debug(MODNAME, "Marked {} as the lower case version of {}",
- GetPrintable(lower), GetPrintable(upper));
- }
-
- charset = codepagetag->getString("charset");
- std::swap(codepage, newcodepage);
- ServerInstance->IsNick = [this](const std::string_view& nick) { return codepage->IsValidNick(nick); };
- CheckInvalidNick();
-
- ServerInstance->Config->CaseMapping = name;
- national_case_insensitive_map = codepage->casemap;
- if (newcodepage) // nullptr on first read.
- {
- CheckDuplicateChan();
- CheckDuplicateNick();
- CheckRehash(newcodepage->casemap);
- }
- }
-
- void OnBuildISupport(ISupport::TokenMap& tokens) override
- {
- if (!charset.empty())
- tokens["CHARSET"] = charset;
- }
-
- void GetLinkData(LinkData& data, std::string& compatdata) override
- {
- codepage->GetLinkData(data, compatdata);
- }
-};
-
-MODULE_INIT(ModuleCodepage)