summaryrefslogtreecommitdiff
path: root/src/modules/m_codepage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/m_codepage.cpp')
-rw-r--r--src/modules/m_codepage.cpp212
1 files changed, 212 insertions, 0 deletions
diff --git a/src/modules/m_codepage.cpp b/src/modules/m_codepage.cpp
new file mode 100644
index 000000000..1c3ac02da
--- /dev/null
+++ b/src/modules/m_codepage.cpp
@@ -0,0 +1,212 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2020 Sadie Powell <sadie@witchery.services>
+ * Copyright (C) 2014 Googolplexed <googol@googolplexed.net>
+ *
+ * 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"
+
+typedef std::bitset<UCHAR_MAX + 1> AllowedChars;
+
+namespace
+{
+ // The characters which are allowed in nicknames.
+ AllowedChars allowedchars;
+
+ // The characters which are allowed at the front of a nickname.
+ AllowedChars allowedfrontchars;
+
+ // The mapping of lower case characters to upper case characters.
+ unsigned char casemap[UCHAR_MAX];
+
+ bool IsValidNick(const std::string& nick)
+ {
+ if (nick.empty() || nick.length() > ServerInstance->Config->Limits.NickMax)
+ return false;
+
+ for (std::string::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;
+ }
+}
+
+class ModuleCodepage
+ : public Module
+{
+ private:
+ // The character map which was set before this module was loaded.
+ const unsigned char* origcasemap;
+
+ // The IsNick handler which was set before this module was loaded.
+ TR1NS::function<bool(const std::string&)> origisnick;
+
+ template <typename T>
+ void RehashHashmap(T& hashmap)
+ {
+ T newhash(hashmap.bucket_count());
+ for (typename T::const_iterator i = hashmap.begin(); i != hashmap.end(); ++i)
+ newhash.insert(std::make_pair(i->first, i->second));
+ hashmap.swap(newhash);
+ }
+
+ void CheckInvalidNick()
+ {
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
+ {
+ LocalUser* user = *iter;
+ if (user->nick != user->uuid && !ServerInstance->IsNick(user->nick))
+ user->ChangeNick(user->uuid);
+ }
+ }
+
+ void CheckRehash(unsigned char* prevmap)
+ {
+ if (!memcmp(prevmap, national_case_insensitive_map, sizeof(origcasemap)))
+ return;
+
+ RehashHashmap(ServerInstance->Users.clientlist);
+ RehashHashmap(ServerInstance->Users.uuidlist);
+ RehashHashmap(ServerInstance->chanlist);
+ }
+
+ public:
+ ModuleCodepage()
+ : origcasemap(national_case_insensitive_map)
+ , origisnick(ServerInstance->IsNick)
+ {
+ }
+
+ ~ModuleCodepage()
+ {
+ ServerInstance->IsNick = origisnick;
+ CheckInvalidNick();
+
+ national_case_insensitive_map = origcasemap;
+ CheckRehash(casemap);
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ const std::string name = ServerInstance->Config->ConfValue("codepage")->getString("name");
+ if (name.empty())
+ throw ModuleException("<codepage:name> is a required field!");
+
+ AllowedChars newallowedchars;
+ AllowedChars newallowedfrontchars;
+ ConfigTagList cpchars = ServerInstance->Config->ConfTags("cpchars");
+ for (ConfigIter i = cpchars.first; i != cpchars.second; ++i)
+ {
+ ConfigTag* tag = i->second;
+
+ unsigned char begin = tag->getUInt("begin", tag->getUInt("index", 0), 1, UCHAR_MAX);
+ if (!begin)
+ throw ModuleException("<cpchars> tag without index or begin specified at " + tag->getTagLocation());
+
+ unsigned char end = tag->getUInt("end", begin, 1, UCHAR_MAX);
+ if (begin > end)
+ throw ModuleException("<cpchars:begin> must be lower than <cpchars:end> at " + tag->getTagLocation());
+
+ bool front = tag->getBool("front", false);
+ for (unsigned short pos = begin; pos <= end; ++pos)
+ {
+ if (pos == '\n' || pos == '\r' || pos == ' ')
+ {
+ throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden character: %u at %s",
+ pos, tag->getTagLocation().c_str()));
+ }
+
+ if (front && (pos == ':' || isdigit(pos)))
+ {
+ throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden front character: %u at %s",
+ pos, tag->getTagLocation().c_str()));
+ }
+
+ newallowedchars.set(pos);
+ newallowedfrontchars.set(pos, front);
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as allowed (front: %s)",
+ pos, pos, front ? "yes" : "no");
+ }
+ }
+
+ unsigned char newcasemap[UCHAR_MAX];
+ for (size_t i = 0; i < UCHAR_MAX; ++i)
+ newcasemap[i] = i;
+ ConfigTagList cpcase = ServerInstance->Config->ConfTags("cpcase");
+ for (ConfigIter i = cpcase.first; i != cpcase.second; ++i)
+ {
+ ConfigTag* tag = i->second;
+
+ unsigned char lower = tag->getUInt("lower", 0, 1, UCHAR_MAX);
+ if (!lower)
+ throw ModuleException("<cpcase:lower> is required at " + tag->getTagLocation());
+
+ unsigned char upper = tag->getUInt("upper", 0, 1, UCHAR_MAX);
+ if (!upper)
+ throw ModuleException("<cpcase:upper> is required at " + tag->getTagLocation());
+
+ newcasemap[upper] = lower;
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as the lower case version of %u (%c)",
+ lower, lower, upper, upper);
+ }
+
+ std::swap(allowedchars, newallowedchars);
+ std::swap(allowedfrontchars, newallowedfrontchars);
+ std::swap(casemap, newcasemap);
+
+ ServerInstance->IsNick = &IsValidNick;
+ CheckInvalidNick();
+
+ ServerInstance->Config->CaseMapping = name;
+ national_case_insensitive_map = casemap;
+ CheckRehash(newcasemap);
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ std::stringstream linkdata;
+
+ linkdata << "front=";
+ for (size_t i = 0; i < allowedfrontchars.size(); ++i)
+ if (allowedfrontchars[i])
+ linkdata << static_cast<unsigned char>(i);
+
+ linkdata << "&middle=";
+ for (size_t i = 0; i < allowedchars.size(); ++i)
+ if (allowedchars[i])
+ linkdata << static_cast<unsigned char>(i);
+
+ linkdata << "&map=";
+ for (size_t i = 0; i < sizeof(casemap); ++i)
+ if (casemap[i] != i)
+ linkdata << static_cast<unsigned char>(i) << casemap[i] << ',';
+
+ return Version("Provides support for custom 8-bit codepages", VF_COMMON | VF_VENDOR, linkdata.str());
+ }
+};
+MODULE_INIT(ModuleCodepage)