]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_codepage.cpp
Send the CHARSET token if using a non-ascii casemapping.
[user/henk/code/inspircd.git] / src / modules / m_codepage.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2020 Sadie Powell <sadie@witchery.services>
5  *
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.
9  *
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
13  * details.
14  *
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/>.
17  */
18
19
20 #include "inspircd.h"
21
22 typedef std::bitset<UCHAR_MAX + 1> AllowedChars;
23
24 namespace
25 {
26         // The characters which are allowed in nicknames.
27         AllowedChars allowedchars;
28
29         // The characters which are allowed at the front of a nickname.
30         AllowedChars allowedfrontchars;
31
32         // The mapping of lower case characters to upper case characters.
33         unsigned char casemap[UCHAR_MAX];
34
35         bool IsValidNick(const std::string& nick)
36         {
37                 if (nick.empty() || nick.length() > ServerInstance->Config->Limits.NickMax)
38                         return false;
39
40                 for (std::string::const_iterator iter = nick.begin(); iter != nick.end(); ++iter)
41                 {
42                         unsigned char chr = static_cast<unsigned char>(*iter);
43
44                         // Check that the character is allowed at the front of the nick.
45                         if (iter == nick.begin() && !allowedfrontchars[chr])
46                                 return false;
47
48                         // Check that the character is allowed in the nick.
49                         if (!allowedchars[chr])
50                                 return false;
51                 }
52
53                 return true;
54         }
55 }
56
57 class ModuleCodepage
58         : public Module
59 {
60  private:
61         // The character map which was set before this module was loaded.
62         const unsigned char* origcasemap;
63
64         // The name of the character map which was set before this module was loaded.
65         const std::string origcasemapname;
66
67         // The IsNick handler which was set before this module was loaded.
68         const TR1NS::function<bool(const std::string&)> origisnick;
69
70         // The character set used for the codepage.
71         std::string charset;
72
73         template <typename T>
74         void RehashHashmap(T& hashmap)
75         {
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);
80         }
81
82         void CheckDuplicateNick()
83         {
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)
87                 {
88                         LocalUser* user = *iter;
89                         if (user->nick != user->uuid && !duplicates.insert(user->nick).second)
90                         {
91                                 user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer available.");
92                                 user->ChangeNick(user->uuid);
93                         }
94                 }
95         }
96
97         void CheckInvalidNick()
98         {
99                 const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
100                 for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
101                 {
102                         LocalUser* user = *iter;
103                         if (user->nick != user->uuid && !ServerInstance->IsNick(user->nick))
104                         {
105                                 user->WriteNumeric(RPL_SAVENICK, user->uuid, "Your nickname is no longer valid.");
106                                 user->ChangeNick(user->uuid);
107                         }
108                 }
109         }
110
111         void CheckRehash(unsigned char* prevmap)
112         {
113                 if (!memcmp(prevmap, national_case_insensitive_map, UCHAR_MAX))
114                         return;
115
116                 RehashHashmap(ServerInstance->Users.clientlist);
117                 RehashHashmap(ServerInstance->Users.uuidlist);
118                 RehashHashmap(ServerInstance->chanlist);
119         }
120
121  public:
122         ModuleCodepage()
123                 : origcasemap(national_case_insensitive_map)
124                 , origcasemapname(ServerInstance->Config->CaseMapping)
125                 , origisnick(ServerInstance->IsNick)
126         {
127         }
128
129         ~ModuleCodepage()
130         {
131                 ServerInstance->IsNick = origisnick;
132                 CheckInvalidNick();
133
134                 ServerInstance->Config->CaseMapping = origcasemapname;
135                 national_case_insensitive_map = origcasemap;
136                 CheckDuplicateNick();
137                 CheckRehash(casemap);
138
139                 ServerInstance->ISupport.Build();
140         }
141
142         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
143         {
144                 ConfigTag* codepagetag = ServerInstance->Config->ConfValue("codepage");
145
146                 const std::string name = codepagetag->getString("name");
147                 if (name.empty())
148                         throw ModuleException("<codepage:name> is a required field!");
149
150                 AllowedChars newallowedchars;
151                 AllowedChars newallowedfrontchars;
152                 ConfigTagList cpchars = ServerInstance->Config->ConfTags("cpchars");
153                 for (ConfigIter i = cpchars.first; i != cpchars.second; ++i)
154                 {
155                         ConfigTag* tag = i->second;
156
157                         unsigned char begin = tag->getUInt("begin", tag->getUInt("index", 0), 1, UCHAR_MAX);
158                         if (!begin)
159                                 throw ModuleException("<cpchars> tag without index or begin specified at " + tag->getTagLocation());
160
161                         unsigned char end = tag->getUInt("end", begin, 1, UCHAR_MAX);
162                         if (begin > end)
163                                 throw ModuleException("<cpchars:begin> must be lower than <cpchars:end> at " + tag->getTagLocation());
164
165                         bool front = tag->getBool("front", false);
166                         for (unsigned short pos = begin; pos <= end; ++pos)
167                         {
168                                 if (pos == '\n' || pos == '\r' || pos == ' ')
169                                 {
170                                         throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden character: %u at %s",
171                                                 pos, tag->getTagLocation().c_str()));
172                                 }
173
174                                 if (front && (pos == ':' || isdigit(pos)))
175                                 {
176                                         throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden front character: %u at %s",
177                                                 pos, tag->getTagLocation().c_str()));
178                                 }
179
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");
184                         }
185                 }
186
187                 unsigned char newcasemap[UCHAR_MAX];
188                 for (size_t i = 0; i < UCHAR_MAX; ++i)
189                         newcasemap[i] = i;
190                 ConfigTagList cpcase = ServerInstance->Config->ConfTags("cpcase");
191                 for (ConfigIter i = cpcase.first; i != cpcase.second; ++i)
192                 {
193                         ConfigTag* tag = i->second;
194
195                         unsigned char lower = tag->getUInt("lower", 0, 1, UCHAR_MAX);
196                         if (!lower)
197                                 throw ModuleException("<cpcase:lower> is required at " + tag->getTagLocation());
198
199                         unsigned char upper = tag->getUInt("upper", 0, 1, UCHAR_MAX);
200                         if (!upper)
201                                 throw ModuleException("<cpcase:upper> is required at " + tag->getTagLocation());
202
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);
206                 }
207
208                 charset = codepagetag->getString("charset");
209                 std::swap(allowedchars, newallowedchars);
210                 std::swap(allowedfrontchars, newallowedfrontchars);
211                 std::swap(casemap, newcasemap);
212
213                 ServerInstance->IsNick = &IsValidNick;
214                 CheckInvalidNick();
215
216                 ServerInstance->Config->CaseMapping = name;
217                 national_case_insensitive_map = casemap;
218                 CheckRehash(newcasemap);
219
220                 ServerInstance->ISupport.Build();
221         }
222
223         void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
224         {
225                 if (!charset.empty())
226                         tokens["CHARSET"] = charset;
227         }
228
229         Version GetVersion() CXX11_OVERRIDE
230         {
231                 std::stringstream linkdata;
232
233                 linkdata << "front=";
234                 for (size_t i = 0; i < allowedfrontchars.size(); ++i)
235                         if (allowedfrontchars[i])
236                                 linkdata << static_cast<unsigned char>(i);
237
238                 linkdata << "&middle=";
239                 for (size_t i = 0; i < allowedchars.size(); ++i)
240                         if (allowedchars[i])
241                                 linkdata << static_cast<unsigned char>(i);
242
243                 linkdata << "&map=";
244                 for (size_t i = 0; i < sizeof(casemap); ++i)
245                         if (casemap[i] != i)
246                                 linkdata << static_cast<unsigned char>(i) << casemap[i] << ',';
247
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());
249         }
250 };
251 MODULE_INIT(ModuleCodepage)