]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_codepage.cpp
Rebuild the 005 numeric after changing the case mapping.
[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  *   Copyright (C) 2014 Googolplexed <googol@googolplexed.net>
6  *
7  * This file is part of InspIRCd.  InspIRCd is free software: you can
8  * redistribute it and/or modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation, version 2.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20
21 #include "inspircd.h"
22
23 typedef std::bitset<UCHAR_MAX + 1> AllowedChars;
24
25 namespace
26 {
27         // The characters which are allowed in nicknames.
28         AllowedChars allowedchars;
29
30         // The characters which are allowed at the front of a nickname.
31         AllowedChars allowedfrontchars;
32
33         // The mapping of lower case characters to upper case characters.
34         unsigned char casemap[UCHAR_MAX];
35
36         bool IsValidNick(const std::string& nick)
37         {
38                 if (nick.empty() || nick.length() > ServerInstance->Config->Limits.NickMax)
39                         return false;
40
41                 for (std::string::const_iterator iter = nick.begin(); iter != nick.end(); ++iter)
42                 {
43                         unsigned char chr = static_cast<unsigned char>(*iter);
44
45                         // Check that the character is allowed at the front of the nick.
46                         if (iter == nick.begin() && !allowedfrontchars[chr])
47                                 return false;
48
49                         // Check that the character is allowed in the nick.
50                         if (!allowedchars[chr])
51                                 return false;
52                 }
53
54                 return true;
55         }
56 }
57
58 class ModuleCodepage
59         : public Module
60 {
61  private:
62         // The character map which was set before this module was loaded.
63         const unsigned char* origcasemap;
64
65         // The name of the character map which was set before this module was loaded.
66         const std::string origcasemapname;
67
68         // The IsNick handler which was set before this module was loaded.
69         const TR1NS::function<bool(const std::string&)> origisnick;
70
71         template <typename T>
72         void RehashHashmap(T& hashmap)
73         {
74                 T newhash(hashmap.bucket_count());
75                 for (typename T::const_iterator i = hashmap.begin(); i != hashmap.end(); ++i)
76                         newhash.insert(std::make_pair(i->first, i->second));
77                 hashmap.swap(newhash);
78         }
79
80         void CheckInvalidNick()
81         {
82                 const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
83                 for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
84                 {
85                         LocalUser* user = *iter;
86                         if (user->nick != user->uuid && !ServerInstance->IsNick(user->nick))
87                                 user->ChangeNick(user->uuid);
88                 }
89         }
90
91         void CheckRehash(unsigned char* prevmap)
92         {
93                 if (!memcmp(prevmap, national_case_insensitive_map, UCHAR_MAX))
94                         return;
95
96                 RehashHashmap(ServerInstance->Users.clientlist);
97                 RehashHashmap(ServerInstance->Users.uuidlist);
98                 RehashHashmap(ServerInstance->chanlist);
99         }
100
101  public:
102         ModuleCodepage()
103                 : origcasemap(national_case_insensitive_map)
104                 , origcasemapname(ServerInstance->Config->CaseMapping)
105                 , origisnick(ServerInstance->IsNick)
106         {
107         }
108
109         ~ModuleCodepage()
110         {
111                 ServerInstance->IsNick = origisnick;
112                 CheckInvalidNick();
113
114                 ServerInstance->Config->CaseMapping = origcasemapname;
115                 national_case_insensitive_map = origcasemap;
116                 CheckRehash(casemap);
117
118                 ServerInstance->ISupport.Build();
119         }
120
121         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
122         {
123                 const std::string name = ServerInstance->Config->ConfValue("codepage")->getString("name");
124                 if (name.empty())
125                         throw ModuleException("<codepage:name> is a required field!");
126
127                 AllowedChars newallowedchars;
128                 AllowedChars newallowedfrontchars;
129                 ConfigTagList cpchars = ServerInstance->Config->ConfTags("cpchars");
130                 for (ConfigIter i = cpchars.first; i != cpchars.second; ++i)
131                 {
132                         ConfigTag* tag = i->second;
133
134                         unsigned char begin = tag->getUInt("begin", tag->getUInt("index", 0), 1, UCHAR_MAX);
135                         if (!begin)
136                                 throw ModuleException("<cpchars> tag without index or begin specified at " + tag->getTagLocation());
137         
138                         unsigned char end = tag->getUInt("end", begin, 1, UCHAR_MAX);
139                         if (begin > end)
140                                 throw ModuleException("<cpchars:begin> must be lower than <cpchars:end> at " + tag->getTagLocation());
141
142                         bool front = tag->getBool("front", false);
143                         for (unsigned short pos = begin; pos <= end; ++pos)
144                         {
145                                 if (pos == '\n' || pos == '\r' || pos == ' ')
146                                 {
147                                         throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden character: %u at %s",
148                                                 pos, tag->getTagLocation().c_str()));
149                                 }
150
151                                 if (front && (pos == ':' || isdigit(pos)))
152                                 {
153                                         throw ModuleException(InspIRCd::Format("<cpchars> tag contains a forbidden front character: %u at %s",
154                                                 pos, tag->getTagLocation().c_str()));
155                                 }
156
157                                 newallowedchars.set(pos);
158                                 newallowedfrontchars.set(pos, front);
159                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as allowed (front: %s)",
160                                         pos, pos, front ? "yes" : "no");
161                         }
162                 }
163
164                 unsigned char newcasemap[UCHAR_MAX];
165                 for (size_t i = 0; i < UCHAR_MAX; ++i)
166                         newcasemap[i] = i;
167                 ConfigTagList cpcase = ServerInstance->Config->ConfTags("cpcase");
168                 for (ConfigIter i = cpcase.first; i != cpcase.second; ++i)
169                 {
170                         ConfigTag* tag = i->second;
171
172                         unsigned char lower = tag->getUInt("lower", 0, 1, UCHAR_MAX);
173                         if (!lower)
174                                 throw ModuleException("<cpcase:lower> is required at " + tag->getTagLocation());
175
176                         unsigned char upper = tag->getUInt("upper", 0, 1, UCHAR_MAX);
177                         if (!upper)
178                                 throw ModuleException("<cpcase:upper> is required at " + tag->getTagLocation());
179
180                         newcasemap[upper] = lower;
181                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Marked %u (%c) as the lower case version of %u (%c)",
182                                 lower, lower, upper, upper);
183                 }
184
185                 std::swap(allowedchars, newallowedchars);
186                 std::swap(allowedfrontchars, newallowedfrontchars);
187                 std::swap(casemap, newcasemap);
188
189                 ServerInstance->IsNick = &IsValidNick;
190                 CheckInvalidNick();
191
192                 ServerInstance->Config->CaseMapping = name;
193                 national_case_insensitive_map = casemap;
194                 CheckRehash(newcasemap);
195
196                 ServerInstance->ISupport.Build();
197         }
198
199         Version GetVersion() CXX11_OVERRIDE
200         {
201                 std::stringstream linkdata;
202
203                 linkdata << "front=";
204                 for (size_t i = 0; i < allowedfrontchars.size(); ++i)
205                         if (allowedfrontchars[i])
206                                 linkdata << static_cast<unsigned char>(i);
207
208                 linkdata << "&middle=";
209                 for (size_t i = 0; i < allowedchars.size(); ++i)
210                         if (allowedchars[i])
211                                 linkdata << static_cast<unsigned char>(i);
212
213                 linkdata << "&map=";
214                 for (size_t i = 0; i < sizeof(casemap); ++i)
215                         if (casemap[i] != i)
216                                 linkdata << static_cast<unsigned char>(i) << casemap[i] << ',';
217
218                 return Version("Provides support for custom 8-bit codepages", VF_COMMON | VF_VENDOR, linkdata.str());
219         }
220 };
221 MODULE_INIT(ModuleCodepage)