2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2020 Sadie Powell <sadie@witchery.services>
6 * This file is part of InspIRCd. InspIRCd is free software: you can
7 * redistribute it and/or modify it under the terms of the GNU General Public
8 * License as published by the Free Software Foundation, version 2.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 typedef std::bitset<UCHAR_MAX + 1> AllowedChars;
26 // The characters which are allowed in nicknames.
27 AllowedChars allowedchars;
29 // The characters which are allowed at the front of a nickname.
30 AllowedChars allowedfrontchars;
32 // The mapping of lower case characters to upper case characters.
33 unsigned char casemap[UCHAR_MAX];
35 bool IsValidNick(const std::string& nick)
37 if (nick.empty() || nick.length() > ServerInstance->Config->Limits.NickMax)
40 for (std::string::const_iterator iter = nick.begin(); iter != nick.end(); ++iter)
42 unsigned char chr = static_cast<unsigned char>(*iter);
44 // Check that the character is allowed at the front of the nick.
45 if (iter == nick.begin() && !allowedfrontchars[chr])
48 // Check that the character is allowed in the nick.
49 if (!allowedchars[chr])
61 // The character map which was set before this module was loaded.
62 const unsigned char* origcasemap;
64 // The name of the character map which was set before this module was loaded.
65 const std::string origcasemapname;
67 // The IsNick handler which was set before this module was loaded.
68 const TR1NS::function<bool(const std::string&)> origisnick;
70 // The character set used for the codepage.
74 void RehashHashmap(T& hashmap)
76 T newhash(hashmap.bucket_count());
77 for (typename T::const_iterator i = hashmap.begin(); i != hashmap.end(); ++i)
78 newhash.insert(std::make_pair(i->first, i->second));
79 hashmap.swap(newhash);
82 void CheckDuplicateNick()
84 insp::flat_set<std::string, irc::insensitive_swo> duplicates;
85 const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
86 for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
88 LocalUser* user = *iter;
89 if (user->nick != user->uuid && !duplicates.insert(user->nick).second)
91 user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer available.");
92 user->ChangeNick(user->uuid);
97 void CheckInvalidNick()
99 const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
100 for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
102 LocalUser* user = *iter;
103 if (user->nick != user->uuid && !ServerInstance->IsNick(user->nick))
105 user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer valid.");
106 user->ChangeNick(user->uuid);
111 void CheckRehash(unsigned char* prevmap)
113 if (!memcmp(prevmap, national_case_insensitive_map, UCHAR_MAX))
116 RehashHashmap(ServerInstance->Users.clientlist);
117 RehashHashmap(ServerInstance->Users.uuidlist);
118 RehashHashmap(ServerInstance->chanlist);
123 : origcasemap(national_case_insensitive_map)
124 , origcasemapname(ServerInstance->Config->CaseMapping)
125 , origisnick(ServerInstance->IsNick)
131 ServerInstance->IsNick = origisnick;
134 ServerInstance->Config->CaseMapping = origcasemapname;
135 national_case_insensitive_map = origcasemap;
136 CheckDuplicateNick();
137 CheckRehash(casemap);
139 ServerInstance->ISupport.Build();
142 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
144 ConfigTag* codepagetag = ServerInstance->Config->ConfValue("codepage");
146 const std::string name = codepagetag->getString("name");
148 throw ModuleException("<codepage:name> is a required field!");
150 AllowedChars newallowedchars;
151 AllowedChars newallowedfrontchars;
152 ConfigTagList cpchars = ServerInstance->Config->ConfTags("cpchars");
153 for (ConfigIter i = cpchars.first; i != cpchars.second; ++i)
155 ConfigTag* tag = i->second;
157 unsigned char begin = tag->getUInt("begin", tag->getUInt("index", 0), 1, UCHAR_MAX);
159 throw ModuleException("<cpchars> tag without index or begin specified at " + tag->getTagLocation());
161 unsigned char end = tag->getUInt("end", begin, 1, UCHAR_MAX);
163 throw ModuleException("<cpchars:begin> must be lower than <cpchars:end> at " + tag->getTagLocation());
165 bool front = tag->getBool("front", false);
166 for (unsigned short pos = begin; pos <= end; ++pos)
168 if (pos == '\n' || pos == '\r' || pos == ' ')
170 throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden character: %u at %s",
171 pos, tag->getTagLocation().c_str()));
174 if (front && (pos == ':' || isdigit(pos)))
176 throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden front character: %u at %s",
177 pos, tag->getTagLocation().c_str()));
180 newallowedchars.set(pos);
181 newallowedfrontchars.set(pos, front);
182 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as allowed (front: %s)",
183 pos, pos, front ? "yes" : "no");
187 unsigned char newcasemap[UCHAR_MAX];
188 for (size_t i = 0; i < UCHAR_MAX; ++i)
190 ConfigTagList cpcase = ServerInstance->Config->ConfTags("cpcase");
191 for (ConfigIter i = cpcase.first; i != cpcase.second; ++i)
193 ConfigTag* tag = i->second;
195 unsigned char lower = tag->getUInt("lower", 0, 1, UCHAR_MAX);
197 throw ModuleException("<cpcase:lower> is required at " + tag->getTagLocation());
199 unsigned char upper = tag->getUInt("upper", 0, 1, UCHAR_MAX);
201 throw ModuleException("<cpcase:upper> is required at " + tag->getTagLocation());
203 newcasemap[upper] = lower;
204 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as the lower case version of %u (%c)",
205 lower, lower, upper, upper);
208 charset = codepagetag->getString("charset");
209 std::swap(allowedchars, newallowedchars);
210 std::swap(allowedfrontchars, newallowedfrontchars);
211 std::swap(casemap, newcasemap);
213 ServerInstance->IsNick = &IsValidNick;
216 ServerInstance->Config->CaseMapping = name;
217 national_case_insensitive_map = casemap;
218 CheckRehash(newcasemap);
220 ServerInstance->ISupport.Build();
223 void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
225 if (!charset.empty())
226 tokens["CHARSET"] = charset;
229 Version GetVersion() CXX11_OVERRIDE
231 std::stringstream linkdata;
233 linkdata << "front=";
234 for (size_t i = 0; i < allowedfrontchars.size(); ++i)
235 if (allowedfrontchars[i])
236 linkdata << static_cast<unsigned char>(i);
238 linkdata << "&middle=";
239 for (size_t i = 0; i < allowedchars.size(); ++i)
241 linkdata << static_cast<unsigned char>(i);
244 for (size_t i = 0; i < sizeof(casemap); ++i)
246 linkdata << static_cast<unsigned char>(i) << casemap[i] << ',';
248 return Version("Allows the server administrator to define what characters are allowed in nicknames and how characters should be compared in a case insensitive way.", VF_COMMON | VF_VENDOR, linkdata.str());
251 MODULE_INIT(ModuleCodepage)