diff options
| author | 2024-07-17 00:06:50 +0100 | |
|---|---|---|
| committer | 2024-07-17 00:06:50 +0100 | |
| commit | 889d521e05f2e922683d48c74eb00290e7a2f548 (patch) | |
| tree | b4c56bd4cdf0881601c7e4d6ff571e491af10bf4 /src/modules/m_codepage.cpp | |
| parent | Use std::endian from C++20 in the sha1 module. (diff) | |
| download | inspircd++-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.cpp | 446 |
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) |
