diff options
-rw-r--r-- | docs/conf/codepages/ascii.conf.example | 37 | ||||
-rw-r--r-- | docs/conf/codepages/latin1.conf.example | 42 | ||||
-rw-r--r-- | docs/conf/codepages/rfc1459.conf.example | 41 | ||||
-rw-r--r-- | docs/conf/codepages/strict-rfc1459.conf.example | 40 | ||||
-rw-r--r-- | docs/conf/modules.conf.example | 17 | ||||
-rw-r--r-- | make/template/main.mk | 2 | ||||
-rw-r--r-- | src/modules/m_codepage.cpp | 212 |
7 files changed, 391 insertions, 0 deletions
diff --git a/docs/conf/codepages/ascii.conf.example b/docs/conf/codepages/ascii.conf.example new file mode 100644 index 000000000..7c5ecdd85 --- /dev/null +++ b/docs/conf/codepages/ascii.conf.example @@ -0,0 +1,37 @@ +# This file contains ASCII codepage rules for use with the codepage module. + +<codepage name="ascii"> + +<cpchars index="45"> # - +<cpchars begin="48" end="57"> # 01234567899 +<cpchars begin="65" end="90" front="yes"> # ABCDEFGHIJKLMNOPQRSTUVWXYZ +<cpchars begin="91" end="96" front="yes"> # [\]^_` +<cpchars begin="97" end="122" front="yes"> # abcdefghijklmnopqrstuvwxyz +<cpchars begin="123" end="125" front="yes"> # {|} + +<cpcase lower="97" upper="65"> # a => A +<cpcase lower="98" upper="66"> # b => B +<cpcase lower="99" upper="67"> # c => C +<cpcase lower="100" upper="68"> # d => D +<cpcase lower="101" upper="69"> # e => E +<cpcase lower="102" upper="70"> # f => F +<cpcase lower="103" upper="71"> # g => G +<cpcase lower="104" upper="72"> # h => H +<cpcase lower="105" upper="73"> # i => I +<cpcase lower="106" upper="74"> # j => J +<cpcase lower="107" upper="75"> # k => K +<cpcase lower="108" upper="76"> # l => L +<cpcase lower="109" upper="77"> # m => M +<cpcase lower="110" upper="78"> # n => N +<cpcase lower="111" upper="79"> # o => O +<cpcase lower="112" upper="80"> # p => P +<cpcase lower="113" upper="81"> # q => Q +<cpcase lower="114" upper="82"> # r => R +<cpcase lower="115" upper="83"> # s => S +<cpcase lower="116" upper="84"> # t => T +<cpcase lower="117" upper="85"> # u => U +<cpcase lower="118" upper="86"> # v => V +<cpcase lower="119" upper="87"> # w => W +<cpcase lower="120" upper="88"> # x => X +<cpcase lower="121" upper="89"> # y => Y +<cpcase lower="122" upper="90"> # z => Z diff --git a/docs/conf/codepages/latin1.conf.example b/docs/conf/codepages/latin1.conf.example new file mode 100644 index 000000000..3beb002fd --- /dev/null +++ b/docs/conf/codepages/latin1.conf.example @@ -0,0 +1,42 @@ +# This file contains ISO 8859-1 codepage rules for use with the codepage module. + +<codepage name="latin1"> + +<cpchars begin="192" end="214" front="yes"> # ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ +<cpchars begin="216" end="246" front="yes"> # ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö +<cpchars begin="248" end="255" front="yes"> # øùúûüýþÿ + +<cpcase lower="83" upper="223"> # ß => S +<cpcase lower="192" upper="224"> # à => À +<cpcase lower="193" upper="225"> # á => Á +<cpcase lower="194" upper="226"> # â => Â +<cpcase lower="195" upper="227"> # ã => Ã +<cpcase lower="196" upper="228"> # ä => Ä +<cpcase lower="197" upper="229"> # å => Å +<cpcase lower="198" upper="230"> # æ => Æ +<cpcase lower="199" upper="231"> # ç => Ç +<cpcase lower="200" upper="232"> # è => È +<cpcase lower="201" upper="233"> # é => É +<cpcase lower="202" upper="234"> # ê => Ê +<cpcase lower="203" upper="235"> # ë => Ë +<cpcase lower="204" upper="236"> # ì => Ì +<cpcase lower="205" upper="237"> # í => Í +<cpcase lower="206" upper="238"> # î => Î +<cpcase lower="207" upper="239"> # ï => Ï +<cpcase lower="208" upper="240"> # ð => Ð +<cpcase lower="209" upper="241"> # ñ => Ñ +<cpcase lower="210" upper="242"> # ò => Ò +<cpcase lower="211" upper="243"> # ó => Ó +<cpcase lower="212" upper="244"> # ô => Ô +<cpcase lower="213" upper="245"> # õ => Õ +<cpcase lower="214" upper="246"> # ö => Ö +<cpcase lower="216" upper="248"> # ø => Ø +<cpcase lower="217" upper="249"> # ù => Ù +<cpcase lower="218" upper="250"> # ú => Ú +<cpcase lower="219" upper="251"> # û => Û +<cpcase lower="220" upper="252"> # ü => Ü +<cpcase lower="221" upper="253"> # ý => Ý +<cpcase lower="222" upper="254"> # þ => Þ + +# Include the ASCII rules to avoid duplication. +<include file="examples/codepages/ascii.conf.example"> diff --git a/docs/conf/codepages/rfc1459.conf.example b/docs/conf/codepages/rfc1459.conf.example new file mode 100644 index 000000000..32f453044 --- /dev/null +++ b/docs/conf/codepages/rfc1459.conf.example @@ -0,0 +1,41 @@ +# This file contains RFC 1459 codepage rules for use with the codepage module. + +<codepage name="rfc1459"> + +<cpchars index="45"> # - +<cpchars begin="48" end="57"> # 01234567899 +<cpchars begin="65" end="90" front="yes"> # ABCDEFGHIJKLMNOPQRSTUVWXYZ +<cpchars begin="91" end="96" front="yes"> # [\]^_` +<cpchars begin="97" end="122" front="yes"> # abcdefghijklmnopqrstuvwxyz +<cpchars begin="123" end="125" front="yes"> # {|} + +<cpcase lower="97" upper="65"> # a => A +<cpcase lower="98" upper="66"> # b => B +<cpcase lower="99" upper="67"> # c => C +<cpcase lower="100" upper="68"> # d => D +<cpcase lower="101" upper="69"> # e => E +<cpcase lower="102" upper="70"> # f => F +<cpcase lower="103" upper="71"> # g => G +<cpcase lower="104" upper="72"> # h => H +<cpcase lower="105" upper="73"> # i => I +<cpcase lower="106" upper="74"> # j => J +<cpcase lower="107" upper="75"> # k => K +<cpcase lower="108" upper="76"> # l => L +<cpcase lower="109" upper="77"> # m => M +<cpcase lower="110" upper="78"> # n => N +<cpcase lower="111" upper="79"> # o => O +<cpcase lower="112" upper="80"> # p => P +<cpcase lower="113" upper="81"> # q => Q +<cpcase lower="114" upper="82"> # r => R +<cpcase lower="115" upper="83"> # s => S +<cpcase lower="116" upper="84"> # t => T +<cpcase lower="117" upper="85"> # u => U +<cpcase lower="118" upper="86"> # v => V +<cpcase lower="119" upper="87"> # w => W +<cpcase lower="120" upper="88"> # x => X +<cpcase lower="121" upper="89"> # y => Y +<cpcase lower="122" upper="90"> # z => Z +<cpcase lower="123" upper="91"> # { => [ +<cpcase lower="124" upper="92"> # | => \ +<cpcase lower="125" upper="93"> # } => ] +<cpcase lower="126" upper="94"> # ~ => ^ diff --git a/docs/conf/codepages/strict-rfc1459.conf.example b/docs/conf/codepages/strict-rfc1459.conf.example new file mode 100644 index 000000000..b14e477e8 --- /dev/null +++ b/docs/conf/codepages/strict-rfc1459.conf.example @@ -0,0 +1,40 @@ +# This file contains strict RFC 1459 codepage rules for use with the codepage module. + +<codepage name="rfc1459"> + +<cpchars index="45"> # - +<cpchars begin="48" end="57"> # 01234567899 +<cpchars begin="65" end="90" front="yes"> # ABCDEFGHIJKLMNOPQRSTUVWXYZ +<cpchars begin="91" end="96" front="yes"> # [\]^_` +<cpchars begin="97" end="122" front="yes"> # abcdefghijklmnopqrstuvwxyz +<cpchars begin="123" end="125" front="yes"> # {|} + +<cpcase lower="97" upper="65"> # a => A +<cpcase lower="98" upper="66"> # b => B +<cpcase lower="99" upper="67"> # c => C +<cpcase lower="100" upper="68"> # d => D +<cpcase lower="101" upper="69"> # e => E +<cpcase lower="102" upper="70"> # f => F +<cpcase lower="103" upper="71"> # g => G +<cpcase lower="104" upper="72"> # h => H +<cpcase lower="105" upper="73"> # i => I +<cpcase lower="106" upper="74"> # j => J +<cpcase lower="107" upper="75"> # k => K +<cpcase lower="108" upper="76"> # l => L +<cpcase lower="109" upper="77"> # m => M +<cpcase lower="110" upper="78"> # n => N +<cpcase lower="111" upper="79"> # o => O +<cpcase lower="112" upper="80"> # p => P +<cpcase lower="113" upper="81"> # q => Q +<cpcase lower="114" upper="82"> # r => R +<cpcase lower="115" upper="83"> # s => S +<cpcase lower="116" upper="84"> # t => T +<cpcase lower="117" upper="85"> # u => U +<cpcase lower="118" upper="86"> # v => V +<cpcase lower="119" upper="87"> # w => W +<cpcase lower="120" upper="88"> # x => X +<cpcase lower="121" upper="89"> # y => Y +<cpcase lower="122" upper="90"> # z => Z +<cpcase lower="123" upper="91"> # { => [ +<cpcase lower="124" upper="92"> # | => \ +<cpcase lower="125" upper="93"> # } => ] diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 215f66137..29adff840 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -572,6 +572,20 @@ #<module name="clones"> #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# Codepage module: Allows using a custom 8-bit codepage for nicknames +# and case mapping. +# +# You should include one of the following files to set your codepage: +#<include file="examples/codepages/ascii.conf.example"> +#<include file="examples/codepages/latin1.conf.example"> +#<include file="examples/codepages/rfc1459.conf.example"> +#<include file="examples/codepages/strict-rfc1459.conf.example"> +# +# You can also define a custom codepage. For details on how to do this +# please refer to the docs site: +# https://docs.inspircd.org/3/modules/codepage + +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # Common channels module: Adds user mode +c, which, when set, requires # that users must share a common channel with you to PRIVMSG or NOTICE # you. @@ -1414,6 +1428,9 @@ # National characters module: # 1) Allows using national characters in nicknames. # 2) Allows using custom (national) casemapping over the network. +# +# This module is incredibly poorly written and documented. You should +# probably use the codepage module instead for 8-bit codepages. #<module name="nationalchars"> # # file - Location of the file which contains casemapping rules. If this diff --git a/make/template/main.mk b/make/template/main.mk index 1e6ba5cd0..f71852aa5 100644 --- a/make/template/main.mk +++ b/make/template/main.mk @@ -210,6 +210,7 @@ install: target @-$(INSTALL) -d -g @GID@ -o @UID@ -m $(INSTMODE_DIR) $(BINPATH) @-$(INSTALL) -d -g @GID@ -o @UID@ -m $(INSTMODE_DIR) $(CONPATH) @-$(INSTALL) -d -g @GID@ -o @UID@ -m $(INSTMODE_DIR) $(DATPATH) + @-$(INSTALL) -d -g @GID@ -o @UID@ -m $(INSTMODE_DIR) $(EXAPATH)/codepages @-$(INSTALL) -d -g @GID@ -o @UID@ -m $(INSTMODE_DIR) $(EXAPATH)/providers @-$(INSTALL) -d -g @GID@ -o @UID@ -m $(INSTMODE_DIR) $(EXAPATH)/services @-$(INSTALL) -d -g @GID@ -o @UID@ -m $(INSTMODE_DIR) $(EXAPATH)/sql @@ -230,6 +231,7 @@ endif -$(INSTALL) -g @GID@ -o @UID@ -m $(INSTMODE_TXT) @CONFIGURE_DIRECTORY@/inspircd-genssl.1 $(MANPATH) 2>/dev/null -$(INSTALL) -g @GID@ -o @UID@ -m $(INSTMODE_BIN) tools/genssl $(BINPATH)/inspircd-genssl 2>/dev/null -$(INSTALL) -g @GID@ -o @UID@ -m $(INSTMODE_TXT) docs/conf/*.example $(EXAPATH) + -$(INSTALL) -g @GID@ -o @UID@ -m $(INSTMODE_TXT) docs/conf/codepages/*.example $(EXAPATH)/codepages -$(INSTALL) -g @GID@ -o @UID@ -m $(INSTMODE_TXT) docs/conf/providers/*.example $(EXAPATH)/providers -$(INSTALL) -g @GID@ -o @UID@ -m $(INSTMODE_TXT) docs/conf/services/*.example $(EXAPATH)/services -$(INSTALL) -g @GID@ -o @UID@ -m $(INSTMODE_TXT) docs/sql/*.sql $(EXAPATH)/sql 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) |